41
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SpringBootとスキーマ駆動開発で始めるWeb API 設計開発入門:前編

Last updated at Posted at 2022-11-13

image.png

初めに

まず最初には、どんな環境で行ったのかと、それらの周辺知識をまとめた備忘録になります。
実際にAPI開発を簡単に行なってみてAPI設計についてまとめてみよう的な記事です。

最初の方はスキーマ駆動開発について簡単に説明してますので、別に興味ない場合は API設計の項目から読んで頂ければと思います。ただ、開発の部分とかはスキーマ駆動開発でやるので、知っとかないとコンテキストがおかしなるかもです。
そして、前編では普遍的なお話が多いです。後編からスキーマ駆動での開発を交えていきます。

また、この記事は一応3部作です。特に第1部で紹介する ROA と REST の関係はめっちゃ重要ですし、REST API の実装のための前提知識をまとめています。読んでいただけると幸いです。

1部:HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

2部
SpringBootとスキーマ駆動開発で始めるWeb API 設計開発入門:前編

3部
SpringBootとスキーマ駆動開発で始めるWeb API 設計開発入門:後編

この記事で紹介しないこと

  • SpringBoot 自体の細かい話

この記事で紹介すること

  • WebAPI / RESTAPI / ROA などの普遍的な思想
  • 上記に基づく設計・開発
    • OpenAPI Specification(スキーマ)
    • OpenAPI Generator(スキーマからコードの自動生成)
  • スキーマ開発駆動に関すること全般
  • SpringBoot & Gradle での設定

環境

  1. OpenAPI(Swaager ではありません)
  2. スキーマ駆動開発
  3. Java11 / SpringBoot2.7.5
  4. Gradle
  5. Postman

周辺知識や設定情報

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-12-30-03.png

参考元:Swagger ではない OpenAPI Specification 3.0 による API サーバー開発
参考元:平静を保ち、コードを生成せよ 〜 OpenAPI Generator誕生の背景と軌跡 〜 / gunmaweb34

❐ スキーマ駆動開発(概要)

  • スキーマをビジネス関係者で策定し、スキーマを起点として各役割が並行して開発
    開発チーム全体のワークフロー
  • スキーマは yaml などで定義され、spec ファイルやスキーマファイルと呼ばれる
yaml
openapi: "3.0.0"
info:
  title: TODO API Document
  version: "0.0.1"
  description: TODO API ドキュメント
paths:
  /health:
    get:
      responses:
        '200':
          description: OK
  • openapi: 必須。 Open APIのバージョンを指定するオブジェクトです。
  • info: 必須。 APIのメタデータを定義します。
  • servers: これから記載するAPI仕様書において、APIがどのような環境で提供されるのかを定義したもの。APIを提供するサーバを定義します。
  • tags: APIを分類するタグを定義する。
  • paths: 必須。 APIとして利用可能なパスおよび操作を定義する。
  • security: API全体にかけるセキュリティ要件。
  • components: Open APIの中で利用する様々なオブジェクトをコンポーネント化して再利用可能にする。

    必須項目の openapi: / info: / paths: のみ記述しておけば、APIが作れます。

❐ スキーマとは

  • WebAPIの構造を定義して、ある定められた形式で記述したもの
    • どのエンドポイント(HTTPメソッドとURI)が
    • どんなリクエストを受付けるのか
    • どんなレスポンスを返すのか

❐ OpenAPI Specification

  • プログラミング言語にとらわれない REST API の標準的なインターフェース記述を定義したフォーマット。OAI がリリースしている。
  • スキーマの定義方法が記載されている。必須項目のみ記述すれば最低限のスキーマができる。
  • OpenAPI Specification のフォーマット(参考:Swagger との比較画像)

❐ OAI:OpenAPI Initiative

  • OpenAPI Specification のフォーマット推進団体
    REST API 記述方式の標準化を推進するための団体
    Linux Foundation, Google, IBM, Microsoft 等の企業が参加
    • もともと Swagger が策定していた API 記述方式を OpenAPI Initiative に提供。そのため、周辺ツールは Swagger
  • プロジェクトは Spring Initnalizrで用意(必要に応じて依存関係は追加)
  • 参考ガイド:Spring Boot プロジェクトの作成
    Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-03-21-17-37.png

❐ OpenAPI (Swagger) Editor

  • 作成したスキーマの確認用プラグイン。YAMLとJSONの両フォーマットをサポート。
    スクリーンショット 2022-11-07 14.48.38.png
  • 虫眼鏡アイコンをクリック(Show OpenAPI Preview)
    スクリーンショット 2022-11-07 14.53.09.png
  • ブラウザでスキーマの確認ができる
    スクリーンショット 2022-11-07 15.05.18.png

❐ OpenAPI Generator (Gradle Plugin)

  • APIスキーマを元にコードやドキュメント生成するジェネレーター
  • Generator が対応している OpenAPI Specification は ver 3.0.0
  • id "org.openapi.generator" version "6.2.1" をbuild.gradle に追加し gradle を再読み込みすると openapi tools が追加される

❐ ドキュメントの自動生成

gradle
// api ドキュメント自動生成用Task
task buildApiDoc(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask) {
    generatorName = "html2"
    inputSpec = "$rootDir/src/main/resources/api-schema.yaml".toString()
    outputDir = "$buildDir/apidoc".toString()
}
  • この状態で gradle を再読み込みすると、gradle タブ内にtodo-api>Tasks>other>buildApiDocという task ができるのでbuildApiDocをダブルクリック
    • bulid 成功したら index.htmlというファイルが自動生成される
      スクリーンショット 2022-11-07 15.25.27.png
  • build/apidoc/index.htmlの中身
    スクリーンショット 2022-11-07 16.19.00.png
  • build/apidoc/index.htmlをブラウザで確認する
    スクリーンショット 2022-11-07 16.19.38.png

❐ スキーマから Spring コードの自動生成を行う

  • タスクを複数設定するには→参照:Generate multiple sources
  • 以下を build.gradle に追加
    • generatorName
      codegen を処理するジェネレーターの名前
      spring や kotlin など指定可能
    • inputSpec
      定義となるスキーマファイルのパス
      APIの挙動をまとめたドキュメント。YAML または json で記述され、spec ファイルと呼ぶ
    • outputDir
      生成物の出力パスとディレクトリ名
    • apiPackage
      生成された API クラスのパッケージ
    • modelPackage
      生成されたモデル クラスのパッケージ
    • interfaceOnly: "true"
      インターフェイス作成
    • 参考:OpenAPI Generator Gradle Plugin
gradle
// Spring コードの自動生成用Task
task buildSpringServer(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask) {
    generatorName = "spring"
    inputSpec = "$rootDir/src/main/resources/api-schema.yaml".toString()
    outputDir = "$buildDir/spring".toString()
    apiPackage = "com.example.todoapi.controller"
    modelPackage = "com.example.todoapi.model"
    configOptions = [
        interfaceOnly: "true"
	]
}
  • この状態で gradle を再読み込みすると、gradle タブ内にtodo-api>Tasks>other>buildSpringServerという task ができるのでbuildSpringServerをダブルクリック
    • bulid 成功したら `HealthApi.java``というインターフェイスが自動生成される。implements し使用するだけ。
      スクリーンショット 2022-11-07 16.08.09.png
java
/**
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (6.2.1).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */
package com.example.todoapi.controller;

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-11-03T21:02:46.816749+09:00[Asia/Tokyo]")
@Validated
@Tag(name = "health", description = "the health API")
public interface HealthApi {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();
    }

    /**
     * GET /health
     *
     * @return OK (status code 200)
     */
    @Operation(
        operationId = "healthGet",
        responses = {
            @ApiResponse(responseCode = "200", description = "OK")
        }
    )
    @RequestMapping(
        method = RequestMethod.GET,
        value = "/health"
    )
    default ResponseEntity<Void> healthGet(
        
    ) {
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

    }

}

❐ Gradle タスクの依存関係設定

  • 自動生成されたコードを参照して Spring アプリケーションを作る
    • Java のコンパイル以前に自動生成タスクが完了している必要がある
  • Gradle タスクの依存関係を定義する
    • Java ファイルのコンパイル時に Spring コードの自動生成
    • スキーマに変更があれば、コンパイル毎に都度コードが生成される Generation Gap パターン
    • openApiGenerator Gradle プラグインは、下記画像のタスクを同名のプロジェクト拡張として実装しています。これらのタスクを他のタスクの依存関係として宣言したい場合 dependsOnを使用し、タスクの参照が必要です。

❐ 自動生成されたコードを import 可能にする

  • build.gradle に以下を追加
  • sourceSets.main.java.srcDir "$buildDir/spring/src/main/java"
    • 自動生成されたファイルのパスを指定しソースディレクトリを sourceSet に登録する。この設定により、パスに置かれた Java ソースコードがコンパイルされるようになります。
    • SourceSets は、Java ソース ファイルの構造を定義する
    • 引用元:Gradle SourceSets Example

    SourceSet は、実行のために一緒にコンパイルおよびアセンブルされる Java ソースファイルおよび追加のリソースファイルのコレクションです。ソースセットの主なアイデアは、プロジェクトにとって共通の意味を持つファイルをグループ化することであり、別のプロジェクトでファイルを分離する必要はありません。

❐ アプリケーション起動に必要なライブラリを追加する

  • OpenAPI Generator で生成されたコードを使用する場合はライブラリを新しく追加する必要がある。
  • build.gradle の dependencys セクションに以下を追加
    • implementation 'org.openapitools:jackson-databind-nullable:0.2.2'
    • compileOnly 'io.swagger.core.v3:swagger-annotations:2.0.0-rc4'
    • implementation 'org.springframework.boot:spring-boot-starter-validation'
  • これをせずに起動した場合下記のエラーコード
/Users/ユーザー名/todo-api/build/spring/src/main/java/com/example/todoapi/controller/HealthApi.java:8: エラー: パッケージio.swagger.v3.oas.annotationsは存在しません
import io.swagger.v3.oas.annotations.Operation;

❐ POSTMAN の使用

  • Postman API Platform | Sign Up for Free

  • Postman概要

    • PostmanはWebAPI開発用のツールです。API 開発のためのコラボレーションプラットフォーム
    • Requestの内容やTokenを設定し、APIに送ることでResponseが返ってきます。
      1. API Client:REST、SOAP、および GraphQLのRequestを直接迅速かつ簡単にテストできます。
      2. Automated Testing:自動テスト機能を提供します。
      3. Design & Mock:Mock Server機能を提供し、エンドポイントとその応答をシミュレートし、API の予想される動作を伝えます。
      4. Documentation:PostmanのAPI定義から自動的にAPIドキュメントを生成します。
      5. Monitors:定期的パフォーマンスと応答をにチェックし、APIのアップデートや変更のモニタリング機能を提供します。
      6. Workspaces:チーム間でAPIを共同開発機能を提供します。
  • 上記で自動生成したインターフェイスを実装しましょう。

java
package com.example.todoapi.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

// 合成アノテーション @ResponseBodyが含まれる 
// ハンドラーメソッドの戻り値がレスポンスボディに書き出されるアノテーション
// Postman でレスポンスボディ見れるよになる
@RestController
public class HealthController implements HealthApi {

    // インターフェイスでは デフォルト実装されているメソッド。まだ実装されていないというモック
    // return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    @Override
    public ResponseEntity<Void> healthGet() {
        return ResponseEntity.ok().build();
    }
}
  • Postman でlocalhost:8080/healthを打ち込むとレスポンスコードがかえってるのが見えます。
    image.png
  • 自動生成されたインターフェイスにレスポンスされる内容やエンドポイントがありましたね。これは spec ファイルに定義したスキーマに基づいています。
java
    /**
     * GET /health
     *
     * @return OK (status code 200)
     */
    @Operation(
        operationId = "healthGet",
        responses = {
            @ApiResponse(responseCode = "200", description = "OK")
        }
    )
    @RequestMapping(
        method = RequestMethod.GET,
        value = "/health"
    )
    default ResponseEntity<Void> healthGet(
        
    ) {
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

    }

6. Web API とAPI記述言語

API とは、ソフトウェア、サービス、プラットフォームなど、全く異なる 2 つのコンポーネントが要求応答メッセージを通じて相互にやりとりするためのインタフェース。

特に 「Web API」でいえば、HTTP(s)プロトコルでやりとりするアプリケーション間のインターフェースのことでソフトウェアの Web インターフェイスです。HTTP に依存しているため「Web API」と呼びます。API は、どのような種類のものであれ、何よりもまずインターフェイスであり、2 つのシステム、対象者、組織などが出会い、やり取りするポイントです。

他には以下の様な特徴があると言われます。

  • HTTP ないしは HTTPS プロトコルによって通信が行われる
  • 特定の HTTPメソッド (GETやPOSTなど) を用いてアクセスできる
  • 特定の URI において提供される
  • URI のクエリパラメータや HTTPリクエストボディに一貫した呼び出し方の決まりがある
  • HTTPレスポンスのヘッダやボディの表現方法に一定の決まりがある

こうした特徴を持つことによって、特定のプログ ラムから見て使いやすく再利用しやすいインタフェースとなっているわけです。

そして、Web API の大別方法として、Netflix の Web APIの開発担当者による 「SSKDs / LSUDs」が あります。

参考元:The future of API design: The orchestration layer

  • SSKDs (Small Set of Known Developers) :特定少数の開発者たち
  • LSUDs (Large Set of Unknown Developers) :不特定多数の開発者たち

たとえば、モバイルアプリを開発しているチームがあり、そこにモバイルアプリを開発する人と、モバイルアプリが利用する Web API を開発する人がいる場合。ここでは Web API の利用者は「モバイルアプリを開発する人」に限られるので、この場合の Web APIは 「SSKDsなAPI」です。

一方で、GitHub が利用者向けに公開している Web API は、世界中の開発者たちが自由にアクセスするので「LSUDs な API」です。

引用元:Web APIとは何なのか

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-14-44-27.png

また、現在は API といえば Web API を想像するかもしれませんが、厳密には API は Web API を包含してます。

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-14-43-39.png

API はリソースを指定し、特定の操作を要求し、臨んだ結果をクライアント(呼出側)に返却します。後述しますが、ここでいうリソースという概念は非常に重要です。また、APIを使用して特定の操作を要求はしても実差にどの様な処理が行われているかは呼び出す側からは隠蔽されていて、関係がありません。

API はソフトウェアを再利用可能な機能単位に分割し、またHTTPという単純・強力で標準化されたプロトコルを介したネットワーク接続を通じてアクセスできます。これは、誰かが提供している API を世界中の誰でも利用できるという仕組みをもたらしています。

6-1. API記述言語

API を定義して記述するというニーズから、「Swagger」などの API 記述言語(API DL:API DescriptionLanguages)が作られました。
これは、RESTful API には、APIをドキュメントとして取り扱うことを可能にするスキーマが必要であることが明らかになったからだそうです。

スキーマとは、その対象の「ありさま」を定められた形式で記述したものです。 Web API のスキーマとなると、ざっくりと言えば、どこにどんなリクエストを送ったらどんなレスポンスが返ってくるかを記述したものになります。

API DL で作られた API スキーマは、人間もコンピュータも判読可能。これは yaml などで記述している。API スキーマには、RESTful API でできる操作内容と API とのやりとりの方法を記述。プログラミング手順を詳しく説明するのに役立つ仮想の取扱説明書のようなもの。

REST は、SOAP の負荷に対処する軽量なソリューションとして導入された背景があります。REST はネットワークアプリケーション設計を支える設計条件で、HTTP などのステートレス通信プロトコルを使用。そのため REST は使いやすく、柔軟性が高く、高速なので API で非常によく利用されています(昔はおもちゃなどと批判、というか悪口?された時代もあったそうですが)。RESTful API(REST を実装する Web サービス)がもつ製薬である REST の設計原則は以下の六つ

  • 統一インターフェース
  • クライアント/サーバー分離
  • ステートレス
  • キャッシュ可能性
  • 階層化システム・アーキテクチャー
  • コードオンデマンド

このRESTの設計原則が実現する疎結合なシステムは、効率的な設計条件として幅広く受け入れられていますね。
ちなみにREST は アーキテクチャでなく、一連の設計条件であり、「REST アーキテクチャ」というものは存在しません。REST はきわめて一般的な設計条件であり、 特に Web と結び付いているわけではないのです。

ROA が RESTful APIを実装するうえで問題を解決してくれるアーキテクチャでもある。言い換えるとRESTful APIは ROA の実装ということになります(ROAに則りRESTful APIは実装されている)
ROA(Resource Oriented Architecture:リソース指向アーキテクチャ)については過去記事にて紹介しています。

HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

歴史的には、Amazon や Google が取り入れ始めたのが切っ掛けに流行ったそうです。

参考元:コネクターを必要としないAPI 開発を実現する「API スキーマ」
参考書籍:Webを支えWebを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

7. スキーマ駆動開発とは(詳細)

スキーマ駆動開発とは、「APIスキーマをまず初めに定義し、その定義をもとにサーバー側(API)とクライアント側(画面)の開発を並行して進めること」を指します。スキーマを中心に人々が動いていくのがスキーマ駆動開発です。

サーバ・クライアントのそれぞれの開発は定義したスキーマをベースに進めていきます。APIのスキーマを最初に定義することによって、両者の間でAPIの仕様のズレを防ぐことができます。また、仕様に変更がある場合はスキーマの定義を修正し、それぞれの開発に反映させていきます。

引用元:スキーマファースト開発のススメ

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-12-10-33.png

サーバ側を実装してからクライアント側を実装。結合すると不具合があるのでそれぞれ直して、テストしてリリース。

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-12-11-05.png

まず一緒に相談しながらスキーマを定義して、スキーマをもとにパラレルに実装。 結合してもほとんど不具合は見当たらないのでそのままリリース。

となり、認識違いや結合時のトラブルが少なくなるので加速が期待できます。

その他の参考:チームのWeb API開発を最適化するSchema Driven Developmentの解説&実装例

7-1. スキーマ活用シーン

スキーマは Web API開発のあらゆるシーンで活用されます。

❐ サーバの実装

当然ですが、Web APIサーバはスキーマに沿って開発されます。明文化されたスキーマが存在するので、サーバの実装を迷いなく進めることができます。

❐ サーバ実装のテスト

スキーマを読み込んで自動化テストに組み込むことで実装が定義されたスキーマを満たしているか 確認できます。自動化テストを定常的に実行する CI (Continuous Integration、継続的インテグレーション) 環境を整備すれば、もしコードに変更を加えたときにスキーマから外れる挙動になってしまってもすぐに気付くことができます。

❐ ドキュメントの生成

スキーマはプログラムから処理しやすい形式で書 かれるのでそれ自体は必ずしも人間にとって読みや すいものではありませんが、そこから人間向けのド キュメントを自動生成することでクライアントの実装に役立てることができます。

❐ スタブサーバの活用

ドキュメントがあっても、実際にHTTPレスポンスを返してくれる Web API サーバがなければクライアントの実装は進めにくいものです。スキーマを読み込んで動作するスタプサーバがあれば、スキーマを用意できたと同時にクライアントの実装に活用できます。

7-2. 契約が先か?実装が先か?(参考)

スキーマ駆動開発という概念が初登場したのは2015年であり、比較的新しい考え方ですので用語の定義がまとまっていない印象です。とくに、スキーマ駆動開発と同等の用語として、スキーマファースト開発やコントラクトファースト開発などがある様に見えます。このあたりは私個人の理解ですのでご注意ください。

参考元:Design First or Code First: What’s the Best Approach to API Development?

システム間でのAPIの設計に関して、進め方は一般的に二種類あるといわれています。

  • コントラクトファースト開発(スキーマ駆動開発のことと理解しています)
  • コードファースト開発

コントラクトファースト開発では、どのようなデータを取得できるか、そしてその取得のためにどのような手順を描けばよいか、いわゆるインターフェースについて開発の最初に綿密な設計を行い、それに従うようにデータの入出力を行う具体的な処理を検討します。

コードファースト開発では、その逆でデータの処理方法について最初に実装を行い、その後インターフェースについて設計します。

両者にはそれぞれメリット・デメリットが存在し、APIで解決したい技術的・戦略的ニーズによって選択する開発手法は異なります。OpenAPIと呼ばれるRESTful APIをドキュメント化する仕様策定に携わったSwagger社により、次のように説明されています。

❐ コントラクトファースト開発を選択したいケース

コードを書く前にまずAPIのコントラクトを設計することを提唱している。これは比較的新しいアプローチだが、特にAPI記述形式の使用により、急速に普及しつつある。

  • 開発者体験が重要な場合

優れたデザインのAPIは、APIの普及と消費に大きな効果をもたらす。もしあなたのAPI戦略がAPIの高い普及率と、あなたのAPIと統合するユーザーの保持を含むなら、良いデベロッパーエクスペリエンス(DX)は重要である。効果的なAPIデザインは、エンドユーザがAPIのリソースと価値提案を素早く理解し、APIとの統合にかかる時間を短縮するのに役立つ。一貫したデザインのAPIは、APIを統合する際の学習曲線を減少させ、より高い再利用価値とエンゲージメントを持つ可能性を高くする。

  • ミッションクリティカルなAPIを提供する場合

Design Firstアプローチを採用する最大の理由は、APIのターゲットが外部の顧客やパートナーである場合だ。このような場合、APIは最終顧客が提供するサービスを利用するための重要な流通チャネルであり、優れたデザインは顧客満足度を決定する上で重要な役割を果たす。特にオムニチャネルのエコシステムでは、情報の一貫性と手間のかからない消費がビジネスの成功の重要な指標となるため、こうしたAPIは組織のサービスを表現する上で重要な役割を担っているのです。

  • 良好なコミュニケーションを確保する場合

API契約は、APIの目的やAPIのリソースがどのように公開されるかについて、チームメンバー全員の足並みを揃えるための中心的な草案として機能することができる。APIアーキテクチャのバグや問題点をチーム内で特定するのは、人間が読める設計を検査することで容易になる。コードを書く前に設計上の問題を発見することは、実装が完了した後に行うよりもずっと効率的で合理的なアプローチです。

コントラクトファーストアプローチを採用することで、APIの利用者側と提供者側でAPI仕様に基づいたコミュニケーションが可能になり、システム結合でトラブルがちなIF仕様に関する問題の改善が期待されるようです。

※開発者体験とは
API の開発者エクスペリエンス(DX)は、開発者が API を使うときに体験することを表す。開発者エクスペリエンスには、(API を利用するための)登録、(API の機能や API の使い方を知るための)ドキュメント、(問題が起きたときに助けてもらうための)サポートなど、さまざまなトピックが含まれる。しかし、何よりも重要な「API の設計」が正しく行われなければ、どのようなトピックに対する努力もすべて無駄になる。

❐ コードファースト開発を選択したいケース

統的なAPI構築のアプローチで、ビジネス要件が示された後にコードの開発が行われ、最終的にコードからドキュメントが生成される。

  • 開発スピードが重要な場合

開発者は、要件定義書から直接APIをコーディングすれば、より早くAPIの実装を開始することができる。もしあなたのGo-to-Market戦略がAPIプログラムの成功のための重要な要素としてスピードとアジリティを強調しているなら、これは重要なことだ。コードファーストのアプローチでは自動化がずっと容易であるという事実は、多くのライブラリがサーバーコードの足場、機能テスト、デプロイの自動化をサポートしているため、このケースを強化するのに役立つ。

  • 内部使用向けのAPIを開発する場合

APIが、それを構築しているチームや会社によってのみ消費されるのであれば、Code Firstアプローチは理想的な解決策である。特に、開発するAPIが小規模でエンドポイントも少なく、社内だけで使われるような場合はそうだろう

参考:OpenAPI 仕様を活用するための 3 つの一般的なシナリオ
こちらも併せて読むと、スキーマ駆動開発の使い所に対する解像度が上がります。

8. API 設計 『設計はなぜ重要か?』

API は、ソフトウェアの技術的なインターフェイスにすぎないように思えますが、API を利用するソフトウェアを作るのは開発者です。うまく設計されていない インターフェイスは使用しづらく、美しくもなく、また変更が容易ではないのです。十分に活用されなかったり、正しく使われなかったりするだけならまだしも、場合によってはうまく設計されていないインターフェイスはセキュリティ面において危険な場合もあります。だからこそ、どのようなインターフェイスの設計も重要であり、API も例外ではありません。

また、よい API を設計するには、インターフェイスそのものだけでなく、アプリケーションの全体像を考慮に入れる必要があります。難しいですね。。。

APIの設計手順

  1. 提供機能を決める
  2. リソースを特定する
  3. URIを決める
  4. HTTPメソッドを決める
  5. リクエストを設計する
  6. レスポンスを設計する

8-1. 提供機能を決める

API が提供する機能を洗い出す

  1. どんなアプリケーションにしたいのか?どんな課題を解決するのか?
  2. それを表現・実現するための機能は何が必要なのか?

より細かく言えば、

  • API をクライアント(人間・プログラム・API)にとって理解しやすく使いやすいものにするには、ユーザの視点に立って設計する。
  • API 開発者視点に立って API を設計すると、理解しにくく使いにくい API になりやすい。
  • ユーザ視点に立った目標があればは、API 開発において最も明快で強力な基盤となる。
  • クライアントは誰か、何ができるか、それをどのように行うか、そのために何が必要か、何が返ってくるかを特定することは、上記の基盤を構築する上で重要な意味を持つ。

❐ どんなアプリケーションにしたいのか?どんな課題を解決するのか?

ここでは、ユーザのタスク管理を解決するためのソリューションとしてのタスク管理アプリが必要だという設定にします。アプリケーション自体の完成度は今回あまり気にしません。

❐ それを表現・実現するための機能は何が必要なのか?

ここでも、一般的な機能を提供するに留まります。

  1. タスク一覧を取得できる
  2. タスク詳細を確認できる
  3. タスクを登録できる
  4. タスクを削除できる
  5. タスクを更新できる

8-2. リソースを特定する

リソースとは Web API における中心的な存在です。こちらは、過去記事のHTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』で紹介している 『1-3. リソース指向アーキテクチャ:ROA(Resource Oriented Architecture) 』にて紹介しています。

Web上に存在する、名前・名詞を持ったありとあらゆる情報のことリソースと呼びます。Webサーバはリソースを特定の言語(英語、日本語)やフォーマット(XML、JSONなど)で送信しなければならない。こういったリソースの形式を『リソースの表現』と呼びます。

世界中のリソースは、 URIで一意の名前を持つ。全てのリソースはURIで表現する。URIがリソースそのものを⽰す。
URI とは 「Uniform Resource Identifier」 の略。統一リソース識別子といい、リソースを識別するIDのことです。URIを用いることで、プログラムはリソースが表現する情報にアクセス可能 (アドレス可能性)になります。

ROAは、それ自体を参照するに値するものを「リソース」として定義しています。リソースはURIとの関係性が非常に深く、リソースがリソースであるために、リソースは少なくともURI を1つ持っていなければならないという条件があります。

❐ APIに必要なリソースを見つける

データ構造のプロパティをリストアップし、それぞれに名前を付ける、名刺を見つけます。

この場合は単純で、タスクに対して一連の処理を行なっているので、タスクとはリソースの対象であることがわかる。task リソースを操作する API を開発していくことが決まります。リソースが中心になるということが解りますね。

その他、特定すべき項目

  • リソースの名前
    • ユニークかつ自明であればあるほどよい
  • リソースの詳細文(オプション:必要に応じて)
    • 名前や型からはわからないオプションの追加情報
  • 他のリソースとの関連
  • リソースの状態
  • 各種日時
    • プログラミング言語に共通する移植可能な型(string, number, boolean,date, array, object)
  • 必須かどうか
フィールド名 名称 データ型 詳細
domain ドメイン名 string 送信元ドメインを表す文字列。このシステムでは最大で255バイトまでの メインを扱うことが可能
states 状態 object 現在の domain リソースの状態を表すオブジェクト
states.setup セットアップ済みフラグ boolean この domain リソースの環境設定が完了しているかどうかを表すフラグ、初期値は false
states.available 利用可能フラグ boolean この domain リソースが利用可能かどうかを表すフラグ。 初期値はfalse
created リソースの作成日 string リソースが作成された日時を表す RFC 3339 形式の文字列。 GMT 表現となる
updated リソースの更新日 string リソースが更新された日時を表すRFC3339形式の文字列。 GMT 表現となる
json
{
    "domain": "mail.example.com",
    
    "states": {
        "setup": true, 
        "available":true
    }
    
    "created": "2014-06-01T20:12:50", 
    "updated": "2014-06-01T20:23:16"
}

8-3. エンドポイント(URI)の定義

リソースにアクセスする方法を設計していきます。URIと『UniformResourceIdentifier:統一資源識別子』といい、この統一資源識別子によって、リソースは Web 上で一意になり、Web上に存在するデータを、簡単に示すことができるようになります。

インタフェースを定義する際に決めなければならない点は以下になります。

  • エンドポイント(URI)
  • HTTPメソッド
  • パスパラメータかクエリパラメータ

ROA におけるリソースは URI を持つ必要があります。そのため、まずは個々のリソースの所在を表すエンドポイントURI を定義する必要があります。APIを利用するクライアントは、ここで定義した URI に対して HTTPメソッドで示される操作(GET / POST / PUT / DELETE)を行います。さらに、一意なリソースの特定(パスパラメータ)もしくは一意なリソースの詳細(クエリパラメータ)が必要になります。

❐ URI を決定する際の注意点

  1. 唯一の要件は「一意」であることではある。
  2. 以下は『リソースのパスはユーザーフレンドリでなければならない』という考えかたに基づきます
    • リソースのパスは API のユーザーが簡単に解釈できるものでなければならないため、パスに含まれる情報は多ければ多いほどよい(長くてもいいではない)
  3. 英語の複数系名詞を使用する
  4. 大文字は使わず小文字と数字で構成
    • 一区画内で複数の単語を使用する場合はハイフンで繋げる 
  5. 意味が特定できない単語を含めない
    • 省略や一般的な単語ではないものは使用しない
  6. 動詞をできる限り含めない
    • HTTPメソッドで表現する
  7. ルールを統一する
    • 規則性がバラバラだと推測しにくい
  8. エンコードが必要な文字を使わない
  9. パスパラメータとクエリパラメータを適切に使用する
    • パスパラメータは一意のリソースを識別するために必要な情報を入れる。
    • クエリパラメータは一意のリソースを操作して取得する際に必要な情報を入れる。また、大量のデータを送るのには向いていません。
      • 「リクエストの設計」の項で説明します。
  10. 記述的・構造的・明示的
    • リソースとそのURIを直感的に対応させる。ディレクトリ構造に似たURIを公開する。
    • http://www.example.com/software/releases/1.0.3.tar.gz
      http://www.example.com/software/releases/latest.tar.gz
      https://qiita.com/yoshitaro-yoyo/items/03f0296f4ba7011d654f

REST には(一意であること以外に)リソースのパスの設計に関する正式なルールはないが、最も広く採用されているのは次のフォーマット。

/{リソースの種類を表す複数形の名前}/{アイテムの IDなど}

リソースの関係(階層)が明らかになるようなリソースパスを使うことと、リソースの種類を表すためにコレクションに複数形の名前を使うことは、REST のデファクトスタンダードとなっています。

❐ 「動詞をできる限り含めない」 の補足

引用元:RESTful API のおさらい

「名詞をリソースにして」「リソースの CRUD を HTTP Method で表現する」ということから、 URI には動詞は含まれないということを意識する。

あとはリソースの本質を表す形になっていれば問題無い。

本質では無いもの、例えば

  • 状態 (latest とか old とか。リソースの状態は変化する)
  • 作者名 (組織が管理しているリソースの場合、転職等で容易に引き継ぎが発生する)
  • 拡張子 (内部システムを切り替えると URI が変わるなら本質ではない)

等が含まれないようになっているとより望ましい。

上記で紹介しているhttp://www.example.com/software/releases/latest.tar.gzには「状態」「拡張子」入っていますね。これは、過去記事のHTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』でも紹介している URI です。こちらでは同じリソースを指し示す可能性ある場合に URI の価値が希薄化するという例で紹介しています。URI の構造を直感的に認知する上で、http://www.example.com/software/releases/latest.tar.gz分かり易いですが、本質的ではないという点であかんのですね。あちらを立たせばこちらが立たず、ですね。

引用元:クールなURIは変わらない

URIが2年経っても、20年経っても、200年経ってもきちんと働くように設定することは、ウェブマスターの義務です。このためには、考察と組織と積極的な関与が必要になります。

URIが変わってしまうのは、その中に含まれる何らかの情報が変わってしまう場合です。それをどう設計(design)するかということが重要なのです(何? URIを設計する? 私はURIを設計しなければならないのですか? その通り、それについてきちんと考えなければなりません)。設計とは、URIから情報を取り去ってしまうということとほとんど同義です。

また、状態について言及する他の意見も紹介します。

引用元:綺麗なAPIを設計するには気をつけたい5つのポイント

3. バージョンをURIに持たせない

APIのバージョンをどこに持たせるかは常に問題になります。URIに入れた場合、多くは /api/1.0/users のようになります。これは多くの場合、2.0になった時に1.0で動作するライブラリをコピーする必要が出るでしょう。同じようなコードが氾濫することになり、管理が煩雑になります。開発者としても一部は1.0、一部は2.0を使い分ける時に共通したURLが /api/ しかなくなってしまうのは不便です。

そこでHTTPヘッダーやクエリストリングの中にバージョン番号を持たせるのはどうでしょう。よりフレキシブルに変更したいならばクエリストリングになるでしょう。HTTPヘッダーであればURLは共通で使えるようになります。

サーバ側としても既存のシステムがまったく別物に置き換わることはそうそうありませんので、プログラム中でバージョン番号を判定するだけで処理できるようになるでしょう。

8-4. HTTPメソッドを決める

クライアントが自分の意図をサーバーにどのように伝えるか?
どうやってサーバにリソースの操作を指定できるのでしょうか?

サーバーは、特定のリクエストが何らかのデータを取得するためのリクエストであって、そのデータを削除したり別のデータで上書きしたりするためのリクエストではないことをどのようにして知るのでしょうか。

データの操作に関する情報をメソッド情報と呼ぶ。Webサービスでメソッド情報を伝える方法 の1つは、それをHTTPメソッドに含めることです。
HTTPメソッド名の大きな利点は、標準化されているため世界中で使用できることです。

クライアントとリソース間のすべてのやり取りは、いくつかの基本的な HTTPメソッドによって処理されます。どのリソースもこれらのメソッドの一部またはすべてをサポートし、メソッドはそれらをサポートするどのリソースでも同じように機能します。

引用元:Web APIの設計
Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-19-37-37.png

URIとHTTPメソッドを組み合わせ、上記のフォーマットを使用してエンドポイントを設計すると以下の様になります。

メソッド URI リソースへの操作内容
GET /tasks タスク一覧取得
POST /tasks タスク作成(Crud)
GET /tasks/{id} タスク詳細取得(Read)
PUT /tasks/{id} タスク更新(Update)
DELETE /tasks/{id} タスク削除(Delete)

❐ HTTPメソッドを間違うとどうなる?

なぜ、適切なHTTPメソッドでないといけないのでしょうか?
同じURIにアクセスした場合でも、HTTPのメソッドが違うと異なる動作をする可能性があります。

2005年、Google はクライアント側のキャッシュツール であるWeb Accelerator リリースしました。Web Accelerator は参照しているページにリンクされているページを「事前に取得」し、これらのリンク1つをクリックすると、コンピュータがすでにそのページを取得しているので、ページがすばやく読み込まれるといったものでした。

Web上にはリソースを削除する処理をGETメソッドで受け付けていた(GETでDELETE実装したなど)アプリが多数存在していた。事前にリソースを取得してしまう Google の Web Accelerator にそれらのリンクが捕捉された結果、大量にリソースが削除されてしまうということが起きました。

原因
①Web上にGETを上記のように誤用するアプリで溢れていたこと
②Web Accelerator は、GET 操作が安全であることを前提としいた
③Google が現実のWebに対応しないツールをリリースしたこと

GET リクエストがHTTP規格に準拠する方法で処理されるかどうかは、サーバー次第です。つまり、プログラマが GETメソッド を通じて、『安全』ではないリソースの操作を許してしまわないことが必要ですね。

後述しますが、『安全性と冪等性』といった概念が重要になります。

❐ GET 『リソースの取得』

「何らかのデータを取得する」

GET リクエストは、指定したURIの情報を取得するメソッド。リソースに関する情報をリクエストする。情報は、一連のヘッダーと表現として送信される。 クライアントがGETリクエストとともに表現を送信することはありません。

リソースを識別できる場合は、レスポンスコード200 (OK)と表現を送信します。条件付きのGETを必ずサポートする。

利用頻度はおそらく最も高く、Webページ、画像、映像、フィードの取得などがこれに当たります。また、クエリパラメータはブラウザのURL欄に表示されます。

❐ POST 『リソースの作成、追加』

GETに次いで利用頻度が高い

POST リクエストは、既存のリソースから新しいリソースを作成する試みです。 ツリーのルートがそのすべてのリーフノードの親であるのと同様に、既存のリソースはデータ構造の意味において、新しいリソースの親になることができます。

POST リクエストとともに送信される表現は、新しいリソースの初期状態を説明する。PUT リクエストの場合と同様、 POST リクエストに表現を含める必要はない。POST リクエストは、まったく新しいリソースを作成せずに、既存のリソースの状態に追加するために使用することもできる。

  • 表現を解析し、適切な URI を選択し、そこに新しいリソースを作成する。

  • 子リソースの作成

    • POSTの代表的な機能で、あるリソースに対する子リソースを作成することができる。ブログ記事の投稿などで使われる。
  • リソースへのデータの追加

    • 子リソース作成ほどメインで使わないが、既存リソースへのデータの追加もできる。
  • ほかのメソッドでは対応できない処理

    • POSTの3つ目の機能は、ほかのメソッドでは対応できない処理の実行。例として、検索結果を表示するURIが挙げられる。
      http://example.jp/search?q={検索キーワード}
      通常はこのURIをGETできるが、検索キーワードがとても長い場合、URIにキーワードを入れてGETする方式は使えない。これはURIを実装する上で、2000文字の制限があるため。こういうときにPOSTを使う。GETがキーワードをURIに含めるのに対して、POSTはリクエストボディに入れることができる。そのため、どんなに長いキーワードにも対応できます。

上記の様に POST は大きめのデータを送信できます。
また、入力内容はボディにあり GET のようにブラウザの URL には表示されません。ただし、ボディは解析すれば確認できるのでセキュリティ的に安全というわけではありません。

❐ PUT 『リソースの更新、作成』

「別のデータで上書きする」

PUT リクエストは、リソースの状態に関するクライアントの提案です。クライアントは通常、PUT リクエストとともに表現を送信し、サーバーはリソースの状態が表現の内容と一致するように、作成や変更を試みる。 表現を伴わない PUT リクエストは、単にリソースが特定のURI に存在することのアサーション(表明)です。

  • リソースの更新
    新しい内容となる表現をPUTリクエスト共に送信し、既存のリソースの表現を上書きします。

  • リソースの作成
    POSTでもできるリソースの作成がPUTでもできる。例えば [http://example.jp/newitem] がまだ存在しないとする。このとき、PUTは存在しないURIへのリクエストとなるため、サーバは新しくリソースを作成すると解釈する。そして、リクエストが成功すると[201 Created]を返す。
    新しいリソースを作成する場合は、表現を解析し、それに基づいてリソースの初期状態を構成します。/newitem が存在していた場合は、ただの上書き処理になります。

❐ DELETE 『リソースの削除』

「そのデータを削除する」

DELETE リクエストは、リソースがもはや存在すべきではないことを示すクライアントの提案です。クライアントが DELETE リクエストとともに表現を送信することはありません。レスポンスコード200 (OK) を送信する。

❐ PUT メソッドと POST メソッドの違い

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-05-13-58-32.png

◎ 従属リソースの作成について

RESTでは、POST は主に従属リソースを作成するために使用されます。従属リソースとは、ほかの「親」リソースに関連して存在するリソースです。たとえば、ブログは、各ブログをリソース(qiita.com/yoshitaro-yoyo) として提供し、 個々のブログエントリを従属リソース (qiita.com/yoshitaro-yoyo/items/bb8cc631276380b68c13) として提供します。 ブログエントリやデータベースレコードを作成するには、親 (ブログまたはデータベーステーブル) に POST リクエストを送信し、PUT の場合と同様に、この時点でクライアントが維持しているリソースの状態は、サーバで維持しているリソース状態に移行します。

PUT でも従属リソースを作成することは可能です。Wiki やはてなブログなどはその方法でリソースを作成できます。私ははてなブログにて記事を追加する際に自身で URI を指定してから投稿します。
Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-21-54-36.png

https://yoshitaro-yoyo.hatenablog.com/entry/Using_abstractClasse_and_interfaces

(余談)見てください。上記で紹介した URI 設計の注意点を全く守ってません。しかも、スペルもミスってる.....。今回勉強したので今後はないでしょう。。。。ただ/entryははてなブログが指定するデフォルトなんですが複数系じゃないんですよね。なんでなんでしょうか?(余談終わり)

こう言った場合、PUT リクエストで URI を送信しています。

これを踏まえた上で PUT と POST の違いを以下に示します。
クライアントは、新しいリソースのURIをクライアントが決定する場合に PUT を使用し、新しいリソースのURIをサーバーが決定する場合に POST を使用します。つまり、POST の場合、URI の決定権はサーバ側にあります。逆に PUT でリソースを作成する場合、リソースのURIはクライアントが決定します。

一般的に、クライアントがリソースのURIを決定できるということは、クライアントを作るプログラマがサーバの内部実装(URIにどの文字を許すのか、長さの制限はどれくらいかなど)を熟知していなければなりません。そのため、PUTのほうがどうしてもクライアントとサーバとの結合が密になります。

ブログのように、サーバーが URI をより厳密に制御できるアプリケーションについて考えてみます。クライアントは、ブログエントリの作成に必要な情報をすべて収集できますが、それでも、作成されるエントリのURIはわからないはずです。これはおそらく、サーバーが順序付けやデー タベースの内部 ID に基づいて URI を割り当てているためです。最終的な URI は qiita.com/yoshitaro-yoyo/items/1/ になるのか、それともqiita.com/yoshitaro-yoyo/items/1000 になるのでしょうか。最終的なURI は、投稿の時間に基づいて決定されるかもしれません。サーバーは現在の時刻を何時だと考えているのでしょうか。これらは POST の場合はクライアントが知らなくてもよいことですが、PUT の場合は把握する必要があります。

PUT と違い、POST メソッドを使用すれば、クライアントは正確な URI を知らなくても、新しいリソースを 作成することができます。ほとんどの場合、クライアントは「親」リソースのURI さえ知っていればよいのです。サーバーはエンティティボディから表現を取得し、それをもとに「親」リソースの「下」に新しいリソースを作成します。

この種の POST リクエストに対するレスポンスには、通常、201 (Created) の HTTP ステータスコードが含まれます。レスポンスの Location ヘッダーに、新規作成された従属リソースの URIが含まれているはずです。これで、リソースが実際に存在し、クライアントがその URI を知るので、それ以降のリクエストでは、PUT を使用してリソースを変更し、GET を使用してその表現を取得し、 DELETE を使用してリソースを削除することができる様になるはずです。

この観点のみで考えると特別な理由がない限りは、リソースの作成は POST で行い URI もサーバ側で決定する、という設計が望ましいのかなーと思います。

別の観点でも考えてみます。

それは『冪等(べきとう)性』に関する点です。

『安全性』と『冪等性』については、次の項で解説します。

引用元:-stackoverflow- HTTPのPOSTとPUTの違いは何ですか?

PUT は冪等性を仮定するように定義されているため、オブジェクトを 2 回 PUT しても、追加の効果はありません。これは素晴らしいプロパティなので、可能であれば PUT を使用します。PUT-べき等性が実際にサーバーに正しく実装されていることを確認してください。

PUT が冪等であるという事実はいくら強調しても足りないと思います: ネットワークが失敗し、クライアントが自分の要求が通ったかどうか確信が持てない場合、2 回目 (または 100 回目) に送信するだけで済みます。これは一度送信するのとまったく同じ効果があるという HTTP 仕様。

ウェブ上では、次のようなアサーションを見つけることができます。

  • リソースの作成には POST を使用し、リソースの変更には PUT を使用すべきである。
  • リソースの作成にはPUTを使用し、リソースの変更にはPOSTを使用すべきである。

どちらも正しくありません。

PUTとPOSTのどちらを使うかは、アクションのべき等性に基づいて選択するのがよいでしょう。

PUTはリソースを置くことを意味し、与えられたURLで利用可能なものを完全に別のものに置き換えます。定義上、PUTはべき等です。何度やっても結果は同じです。x=5 はべき乗です。リソースが以前に存在したかどうかにかかわらず、PUTすることができます(例えば、CreateやUpdate)。

POSTはリソースを更新したり、補助リソースを追加したり、変更を起こしたりします。POSTは、x++がべき乗でないのと同様にべき乗ではありません。

この引数から、PUTは作成するもののURLがわかっているときに作成するためのものであることがわかります。POSTは、作成したいもののカテゴリーの「ファクトリー」や「マネージャー」のURLがわかっているときに作成するために使うことができます。

ということで

POST /expense-report

とするか

PUT /expense-report/10929

のようになります。

これらをみた上でもう一度考え直すと、新規リソースの URI を決定できるかどうか?というよりも、冪等性が求められるリクエストなのか?という視点の方が重要なのかもしれません。POST による冪等性のないリクエストによる通信がリソースに対してどの様な副作用を引き起こしそれが許容されるかどうか、許容された場合に何が問題となるのかなど考えなくてはいけないんでしょうか。

❐ 『安全性』と『冪等性』

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-22-33-39.png

HTTPメソッドの利用 が ROA に沿っていれば、 安全性 (Safety) と冪等性 (Idempotence) を持つようになっています。
情報工学に置いて安全と冪等(べきとう)を適用すべき対象は「リソースの一点のみ」です。リソースの操作を行うこと、その結果リソースにどの様な変化が起きたのか?焦点はそこのみになります。

※ ROA:REST スタイルのAPI設計においては、ROA (Resource Oriented Architecture) という設計手法が広く知られています。ROAには4つの概念と4つの特徴があります。

「4つの概念」は次のものです。

  • リソース
  • URI (≒名前)
  • 表現 (Representation)
  • リンク

さらに、「4つの特徴」は次のものです。

  • アドレス可能性 (Addressability)
  • ステートレス性 (Stateless)
  • 接続性 (Connectability)
  • 統一インタフェース (Uniform Interface)

Web API を RESTful にするとは、 ROAに沿って計することにほかなりません。したがって、この ROAの概念と特徴について正確に理解しておく必要があります。詳細は過去記事にて紹介しています。

HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

◎ 安全性

GET または HEAD リクエストは、何らかのデータを読み取るためのリクエストであり、サーバーの状態を変更するためのリクエストではありません。クライアントは GET または HEAD リクエスト を10回繰り返すことができるが、そのリクエストを1回だけ実行するのも、1回も実行しないのも同じことです。GET リクエストを送信しても単にその表現を取得するだけです。クライアントは、 未知の URI に GET リクエストや HEAD リクエストを送信しても害がないことに安心できるでしょう。

ただし、GET リクエストや HEAD リクエストに副作用がないとは言い切れません。たとえば一部のリソースは、クライアントから GET リクエストが送信されるたびにインクリメントされるヒットカウンタを含む場合があります。また、ほとんどの Web サーバーは、受信したすべてのリクエストをログファイルに記録します。これらは意図しない副作用で、サーバーの状態やリソースの状態が GET リクエストに応じて変化することを意味します。ですが、クライアントが副作用を要求したわけではないので、クライアントに責任はないと解釈できます。

問題になるのは、クライアントが副作用をあてにし てGET リクエストや HEAD リクエストを送信することと、クライアントがリクエストを送信した際に想定外すぎる副作用を発生させるような場合です。

◎ 冪等性

これは数学に由来する概念です。数学におけるべき等演算とは、1回適用しても複数回適用しても結果が同じもののことです。数字に0を掛けることは冪等であり、4×0x0 × 0は4×0と同じです。つまり、1つのリクエストの実行が同じリクエストを繰り返し実行することと同じである場合、リソースでの操作は冪等であると言えます。

リソースの状態を相対的に変更する表現を PUT することをクライアントに許可してはいけません。リソースがそのリソース状態の一部として数値を維持する場合、クライアントは PUT を使用して、その値を4、0、または-50に設定することは許可しても問題ありませんが、その値を1ずつインクリメントすることはで許可すべきではありません。 初期値が0の場合「値を4に設定する」という PUT リクエストを2回送信しても値は4のままですが、 初期値が0の場合「値を1ずつインクリメントする」とい うPUTリクエストを2回送信すると、値は1ではなく2になります。これは冪等ではありません。

※数字に 1 を掛けることは、安全かつべき等です。4×1×1は、4×1と同じであり、4と同じ。4×0は4と同じではないため、0による乗算は安全ではありません。ほかの数字による乗算は安全でも冪等でもありません。

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-22-34-37.png

・GETと HEAD は読み取り専用で、あるリソースを何回GETしても同じ結果は同じです。またリソースの状態を変化させないので「安全」であり「冪等」です

・POSTはリソースの状態を変化させ、同じリクエストでもリソースの状態変化を予測できません。POST した数だけ新しいリソース作成されるため「安全」でも「冪等」でも有りません。

・PUT と DELETE はリソースの状態を変化させますので「安全」では有りません。それぞれリソースの更新と削除を行います。ですが、たとえば一度削除したリソースに再度削除のリクエストを送ってもリソースが削除されている状態は変わりません。同じリクエストを複数回送信しても結果は変わらないので「冪等」です。

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-04-22-45-14.png

情報工学における「冪等」の定義の存在意義は、システムの堅牢性や情報の整合性を追求するためのものです。

そして、HTTP において「冪等」を定義する目的は、もしクライアントがサーバーからのリクエスト成功の応答を受信する前に通信障害が発生した場合、クライアントはリクエストをリトライしてそのリクエストの目的を達成する必要があり、サーバーはそのリトライによるリソースの不整合から保護しなければなりません。

さらに、クライアントは、同じリクエストをリトライするとリソースの整合性が破壊されるとなれば、リトライはできなくなるでしょうし、そのリトライによりリソースの整合性が保護されるのか、破壊されるのかを知らなければ無闇にリトライもできなくなるでしょう。

したがって、サーバーはこのような堅牢性を確保するとともに、クライアントにリトライ可否の区別を知らせなければなりません。これが本来の「冪等」の目的であり、「リソースの状態の変化」に着目する理由はここにあります。

POST か PUT かを選択する上でこの様な冪等性が引き起こす堅牢性への影響なども考慮しなくてはいけないのが示唆されていますね。

また、上記でも紹介しましたが適切ではないHTTPメソッドの仕様はリソースの意図しない変更を生み出す可能性があります。

❐ HTTP メソッドの誤用は想定外を生み出す

安全性を満たさないGET

[GET] → /notifications/1/open
  • 通知を既読に更新するAPI
    リソースの状態を変えています。これは GET の安全性に違反しています。安全とはリソースの状態を変更しないという事です。

冪等性を満たさないDELETE

[DELETE] → /notifications/latest 
  • 最新の通知を削除
    最新という定義からすると、最新を削除した場合、削除する前の一つ前が次の最新になり、同じリクエストを行なった場合、次々と「最新」のリソースが削除されていきます。冪等とは同じリクエストを行っても結果が変わらないことを意味しますので、この場合の DELETEメソッドの使用は冪等性違反になります。
     これは上記で紹介したインクリメントに類似した現象です。ここではリソースに対してデクリメントな現象が起きています。

Web API の利用側からするとこの様に誤用された HTTPメソッドは想定外の結果を生み出しかねません。上記で紹介した Google の Web Accelerator の様にです。

設計を見直したり、エンドポイントを設定する際は「安全性」と「冪等性」の点を考慮に入れるべきでしょう。

8-5. リクエストを設計する

参考元:Web APIの設計

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-05-16-08-19.png

「HTTPリクエスト」と「HTTPレスポンス」については、以下の記事をご覧ください。とんでもなく解りやすいです。
「HTTPリクエスト」と「HTTPレスポンス」

本記事の事例としては以下のイメージ

POST /tasks HTTP/1.1
Host: example.com
Content-Type: application/json
{"title":"This is task title."}
  • 1行目がエンドポイント
  • 2・3行目がリクエストヘッダでリクエストのメタデータを格納
  • 空行以降はリクエストボディでリクエストに必要な情報、リソースの状態が記述されています。

ヘッダ・リソースの状態などについては、以前投稿した下記の記事に詳細を書いてますので参考にどうぞ。

HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

リクエスト設計では以下の3点を決定します。

  1. パスパラメーラ
    • /API/tasks/11
  2. クエリパラメータ
    • /tasks?title=bug?title=bug
  3. リクエストボディ
    • {"title":"This is task title."}
    • リクエストボディに格納するリソースの形式も設計する

❐ パスパラメータとクエリパラメータの使い分け

参考元:[RESTful API]パスパラメータ、クエリパラメータ、リクエストボディの違いと設計
参考元:パスパラメータとクエリパラメータの違い
参考元:API設計まとめ
参考元:Web APIの設計

上記でも紹介しましたが、

  1. パスパラメータとクエリパラメータを適切に使用する
    ・ パスパラメータは一意のリソースを識別するために必要な情報を入れる。
    ・ クエリパラメータは一意のリソースを操作して取得する際に必要な情報を入れる。

その他に、一意なリソースの特定において必要かどうか?という観点もあります。

例えば、

  • /tasks/999
  • /tasks?id=999

パスパラメータで指定される 999 は クエリパラメータでは省略することができません。省略できないのであればそれは一意なリソースを特定するのに必要なパラメータであり、その場合はパスパラメータで URI を設計すべきです。

「クエリパラメータは一意のリソースを操作して取得する際に必要な情報」なので、一意のリソースを識別したい場合において、/tasks?id=999の様にクエリパラメータを用いるのは、本来の使い方ではないのが解ります。識別した上でそのリソースの状態の一部分に対して何かしらの操作(特定情報の取得や更新や削除)を行いたい場合はクエリパラメータを使用すべきです。

例えば、

  • /tasks/999?feilds=title
  • /tasks/999/title

上記はリソース999のタイトルだけ取得したい様な検索実行時が考えられます。
下記では URI の構造的に999の従属リソースとして title というリソースが存在しているかの様に見えてしまいます。実際には従属リソースではないので、不適切です。リソースの一部分にたいして何かしらの操作を行う場合ですのでクエリパラメータで URI を設計します。
 

8-6. レスポンスを設計する

参考元:Web APIの設計

Spring Boot と OpenAPI で始めるスキーマ駆動開発_2022-11-05-16-08-19.png

HTTP/1.1 200
Host: example.com
Date: ・・・・・・・・・
{"title":"title 1"}

レスポンス設計では以下の3点を決定します。

  1. ステータスライン(HTTPレスポンスコード)
    • HTTP/1.1 200200
  2. レスポンスヘッダ
    • Date: ・・・・・・・・・
  3. レスポンスボディ
    • {"title":"title 1"}
    • レスポンスボディに格納するリソースの形式も設計する

❐ HTTP のレスポンスステータスコード

HTTP のレスポンスステータスコードは、特定の HTTP リクエストが正常に完了したどうかを示します。 サーバからのレスポンスの意味を表す3桁の数字コード。レスポンスは 5 つのクラスに分類されています。

1. 情報レスポンス (処理中) 1xx シリーズのレスポンスコードは、 HTTP サーバーとのネゴシエーションでのみ使用される。
「リクエストは受け取られた。処理は継続される」
2. 成功レスポンス 2xx シリーズのレスポンスコードは、処理が成功したことを示す。
「リクエストは受け取られ、理解され、受理された」
3. リダイレクトメッセージ 3xxステータスコードは、クライアントが要求しているものを取得するには、 さらに作業が必要 であることを示す
「リクエストを完了させるために、追加的な処理が必要」
4. クライアントエラーレスポンス クライアント側で問題が起きていることを示す。 認証、表現の フォーマット、または HTTP ライブラリ自体に問題がある。クライアント側で問題を解決する必要がある。
「クライアントエラークライアントからのリクエストに誤りがあった」
5. サーバーエラーレスポンス 5xxシリーズのステータスコードは、サーバー側の問題を表すために使用される。ほとんどの場合、これらのコードは、サーバーがクライアントのリクエストを実行する、またはリクエストが正しいかどうかを確認する状態にすらなく、クライアントがそのリクエストをあとから再送すべきであることを意味する。クライアントはサーバー上での問題を解決するために何もできない。
「サーバがリクエストの処理に失敗した」

レスポンスコードについては以前投稿した下記の記事に詳細を書いてますので参考にどうぞ。

HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

❐ POST / PUTでもレスポンスボディを返す

引用元:綺麗なAPIを設計するには気をつけたい5つのポイント

APIを使った開発においてよくあるのがPOST/PUTでデータを更新した時に、最新のデータをGETで取り直すというものです。これは2回APIアクセスする必要があってムダです。POSTで作った、PUTで更新したデータについて、その最新の状態をレスポンスに含めるようにしましょう。

特にサーバ側で処理されるデータがある場合、クライアント側ではデータがどういった値を持つかは分かりません。その結果、GETでアクセスする必要が生じてしまいます。ネットワークはボトルネックになりやすいので、クライアントからのアクセスを少なく済むようなサーバ側の実装を心がけましょう。

POST リクエストとともに送信される表現は、新しいリソースの初期状態を説明します。リクエストが成功すればレスポンスコード 201(Created) を送信し、新しいリソースの URI を Location ヘッダーに設定します。

引用元:HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

201 Created 「作成」
重要度: 高
関連HTTPメソッド:POST / PUT
レスポンスヘッダ: Location
ボディ: リクエストが成功し、新しいリソースを作成したことを示し、そのリソースにリンクする。Location ヘッダーを使用してリソースの実際の場所をクライアントに伝える場合は、そのリソースの表現を含むことができる。

サーバーは、クライアントのリクエストに応じて新しいリソースを作成する際に、このステータスコードを送信する。
リクエストが POST だった場合、Location ヘッダには新しいリソースの URI が絶対 URI で入る。

前編はここまで

学習に合わせてになるんで、以降は何をまとめるか未定です。

今のところスキーマ駆動開発におけるスキーマの定義の仕方とか公式ドキュメントの見方とかを執筆途中です。できれば、「APIセキュリティチェック」なども盛り込みたいですができるとは思っていません。

また、上記で紹介しましたが、この記事は3部作の二部です。第3部もご覧いただけると嬉しいです。

1部:HTTPとRESTの基本 『網羅版:HTTPメソッドとレスポンスコード』

2部
SpringBootとスキーマ駆動開発で始めるWeb API 設計開発入門:前編

3部
SpringBootとスキーマ駆動開発で始めるWeb API 設計開発入門:後編

41
38
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?