コンテンツにスキップ

OpenAPI

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. OpenAPI

OpenAPI とは

RESTful-API の仕様を実装により説明するためのフォーマットのこと。JSON 型または YAML 型で実装できる。

いくつかのフィールドから構成されている。

openapi: # openapi フィールド

info: # info フィールド

servers: # servers フィールド

paths: # paths フィールド

webhooks: # webhooks フィールド

components: # components フィールド

security: # security フィールド

tags: # tags フィールド

externalDocs: # externalDocs フィールド


OpenAPI の視覚化

▼ OpenAPI の視覚化とは

OpenAPI を共有して閲覧できるように、仕様書の静的ファイル (例:html ファイル、css ファイル、js ファイル) を自動的に作成する。

さらに、静的な Web サイトとして公開すると、チーム内で共有するとなおよい。

▼ ReDoc の場合

ReDoc を使用して、OpenAPI から静的ファイルを作成する。

openapi: 3.0.0
info:
  version: 1.0.0
  title: Swagger sample
paths:
  /users:
    get:
      summary: Usersを取得するAPIです。
      responses:
        "200":
          description: HTTPステータスコード200と以下の情報を含むJSONオブジェクトを返します。
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id:
                    type: string
                    example: 1234567
$ npm install -g redoc-cli

$ redoc-cli bundle openapi.yaml redoc.html

▼ Swagger UI/Swagger Codegen の場合

Swagger UI/Swagger Codegen を使用して、OpenAPI から静的ファイルを作成する。

▼ OpenAPI Generator の場合

OpenAPI Generator を使用して、OpenAPI から静的ファイルを作成する。

なお、OpenAPI Generator は OpenAPI からコードを作成できる。

$ openapi-generator generate -i docs/openapi.yaml -o docs/html -g html

▼ oapi-codegen の場合

記入中...


API スキーマ駆動開発

▼ API スキーマ駆動開発とは

API 仕様書に基づいて、API を実装する。

▼ OpenAPI Generator の場合

以下の流れに基づいて、API を実装していく。

ここでは、コードの自動作成に OpenAPI Generator を使用するものとする。

  1. OpenAPI に基づいて API 仕様を定義する。

    openapi: 3.0.3
    info:
      description: EC API
      title: EC
      version: 2023.1.1
    servers:
      - description: Online EC Example Service
        url: http://example.com/v1
    tags:
      - description: Product Operation
        name: Product
    paths:
      /products:
        get:
    
        ...
    
      /products/{productId}:
        get:
    
        ...
    
    
    components:
      schemas:
    
      ...
    
  2. OpenAPI Generator を使用して、API 仕様から静的ファイルを自動的に作成する。

    $ openapi-generator generate -i docs/openapi.yaml -o docs/html -g html
    
  3. OpenAPI Generator を使って、API 仕様からコードを自動的に作成する。

    # brew、npmなどでインストールする
    $ brew install openapi-generator
    
    # GoのAPIを作成する
    # openapi-generator generate -i <OpenAPI仕様書> -g <言語、フレームワーク> -p packageVersion=<タグ> -o <出力先ディレクトリ>
    $ openapi-generator generate -i docs/openapi.yaml -g go-gin-server -p packageVersion=0.0.1 -o go-gin
    
    # --template-dirを使用すると、ユーザー定義のテンプレートからコードを作成できる
    
    // テンプレートファイル (mustache形式)
    {{>partial_header}}
    package main
    
    import (
        "log"
        // WARNING!
        // Change this to a fully-qualified import path
        // once you place this file into your project.
        // For example,
        //
        //    sw "github.com/myname/myrepo/{{apiPath}}"
        //
        sw "openapi-gin-server/{{apiPath}}"
    )
    
    func main() {
    
        log.Printf("Server started")
    
        router := sw.NewRouter()
    
        log.Fatal(router.Run(":{{serverPort}}"))
    }
    
  4. 自動的に作成されたファイル (api_product.go) に実際の API を追加実装していく。

    /*
     * EC
     *
     * EC API
     *
     * API version: 2023.1.1
     * Generated by: OpenAPI Generator (https://openapi-generator.tech)
     */
    
    package openapi
    
    import (
        "net/http"
        "github.com/gin-gonic/gin"
    )
    
    // DeleteProductById - Cancel a specified product
    func DeleteProductById(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{})
    }
    
    // GetProductById - Get a specified product
    func GetProductById(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{})
    }
    
    // GetProducts - Get all products
    func GetProducts(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{})
    }
    

▼ oapi-codegen の場合

oapi-codegen -package=openapi -generate=types,client,spec openapi.yaml > api.go


クラウドプロバイダーとの統合

▼ API ゲートウェイの場合

API ゲートウェイによる OpenAPI のインポートについては、以下のリンクを参考にせよ。


02. フィールド

openapi フィールド (必須)

OpenAPI のバージョンを定義する。

openapi: 3.0.0


info フィールド (必須)

API 名、作成者名、メールアドレス、ライセンスなどを定義する。

info:
  title: Foo API # API 名
  description: The API for Foo. # API の説明
  termsOfService: https://www.foo.com/terms/ # 利用規約
  contact:
    name: API support # 連絡先名
    url: https://www.foo.com/support # 連絡先に関する URL
    email: support@foo.com # メールアドレス
  license:
    name: Apache 2.0 # ライセンス
    url: https://www.apache.org/licenses/LICENSE-2.0.html # URL
  version: <バージョンタグ> # API ドキュメントのバージョン


servers フィールド

API 自体の URL などを定義する。

servers:
  - url: https://{env}.foo.com/api/v1
    description: |
    variables:
      env:
        default: stg
        description: API environment
        enum:
          - stg
          - www


paths フィールド (必須)

API のエンドポイント、HTTP メソッド、ステータスコードなどを定義する。

paths:
  #===========================
  # paths オブジェクト
  #===========================
  /users:
    #===========================
    # path item オブジェクト
    #===========================
    get: # GET メソッドを設定する。
      tags:
        - ユーザー情報取得エンドポイント
      summary: ユーザー情報取得
      description: 全ユーザー情報を取得する。
      #===========================
      # リクエスト
      #===========================
      parameters: []
      #===========================
      # レスポンス
      #===========================
      responses:
        "200":
          description: OK レスポンス
          content:
            application/json: # MIME type
              foo: # レスポンスボディ例
                Users:
                  User:
                    userId: 1
                    name: Hiroki
              schema:
                $ref: "#/components/schemas/user" # User モデルを参照する。
        "400":
          description: Bad Request レスポンス
          content:
            application/json: # MIME type
              foo: # レスポンスボディ例
                status: 400
                title: Bad Request
                errors:
                messages: ["不正なリクエストです。"]
              schema:
                $ref: "#/components/schemas/error" # 異常系モデルを参照する。
        "401":
          $ref: "#/components/responses/unauthorized" # 認証エラーを参照する。
    #===========================
    # path item オブジェクト
    #===========================
    post: # POST メソッドを設定する。
      tags:
        - ユーザー情報作成エンドポイント
      summary: ユーザー情報作成
      description: ユーザー情報を作成する。
      #===========================
      # リクエスト
      #===========================
      parameters: []
      requestBody: # メッセージボディにパラメーターを割り当てる。
        description: ユーザーID
        content:
          application/json: # MIME type
            foo: # メッセージボディ例
              userId: 1
            schema: # API スキーマ
              $ref: "#/components/schemas/user" # User モデルを参照する。
      #===========================
      # レスポンス
      #===========================
      responses:
        "200":
          description: OK レスポンス
          content:
            application/json: # MIME type
              foo: # レスポンスボディ例
                userId: 1
              schema:
                $ref: "#/components/schemas/normal" # API スキーマとして、正常系モデルを参照する。
        "400":
          description: Bad Request レスポンス
          content:
            application/json: # MIME type
              foo: # レスポンスボディ例
                status: 400
                title: Bad Request
                errors:
                  messages: ["ユーザーIDは必ず指定してください。"]
              schema:
                $ref: "#/components/schemas/error" # API スキーマとして、異常系モデルを参照する。
        "401":
          $ref: "#/components/responses/unauthorized" # 認証エラーを参照する。
  #===========================
  # paths オブジェクト
  #===========================
  /users/{userId}:
    #===========================
    # path item オブジェクト
    #===========================
    get:
      tags:
        - ユーザー情報取得エンドポイント
      summary: 指定ユーザー情報取得
      description: 指定したユーザー情報を取得する。
      #===========================
      # リクエスト
      #===========================
      parameters:
        - in: path # パスにパラメーターを割り当てる。
          name: userId
          required: "true"
          description: ユーザーID
          schema:
            type: string
            foo: # パスパラメーター例
              userId=1
      #===========================
      # レスポンス
      #===========================
      responses:
        "200":
          description: OK レスポンス
          content:
            application/json: # MIME type
              foo: # ボディ例
                userId: 1
                name: Hiroki
              schema: # API スキーマ
                $ref: "#/components/schemas/user" # User モデルを参照する。
        "400":
          description: Bad Request レスポンス
          content:
            application/json: # MIME type
              foo: # ボディ例
                status: 400
                title: Bad Request
                errors:
                  messages: ["ユーザーIDは必ず指定してください。"]
              schema:
                $ref: "#/components/schemas/error" # 異常系モデルを参照する。
        "401":
          $ref: "#/components/responses/unauthorized" # 認証エラーを参照する。
        "404":
          description: Not Found レスポンス
          content:
            application/json: # MIME type
              foo: # ボディ例
                status: 404
                title: Not Found
                errors:
                  messages: ["対象のユーザーが見つかりませんでした。"]
              schema:
                $ref: "#/components/schemas/error" # 異常系モデルを参照する。
    #===========================
    # path item オブジェクト
    #===========================
    put:
      tags:
        - ユーザー情報更新エンドポイント
      summary: 指定ユーザー更新
      description: 指定したユーザー情報を更新する。
      #===========================
      # リクエスト
      #===========================
      parameters:
        - in: path # パスにパラメーターを割り当てる。
          name: userId
          required: "true"
          description: ユーザーID
          schema:
            type: string
            foo: # パスパラメーター例
              userId=1
      #===========================
      # レスポンス
      #===========================
      responses:
        "200":
          description: OK レスポンス
          content:
            application/json: # Content-Type
              foo: # ボディ例
                userId: 1
                name: Hiroki
              schema: # API スキーマ
                $ref: "#/components/schemas/user" # User モデルを参照する。
        "400":
          description: Bad Request レスポンス
          content:
            application/json: # Content-Type
              foo: # ボディ例
                status: 400
                title: Bad Request
                errors:
                  messages: ["ユーザーIDは必ず指定してください。"]
              schema:
                $ref: "#/components/schemas/error" # 異常系モデルを参照する。
        "401":
          $ref: "#/components/responses/unauthorized" # 認証エラーを参照する。
        "404":
          description: Not Found レスポンス
          content:
            application/json: # Content-Type
              foo: # ボディ例
                status: 404
                title: Not Found
                errors:
                  messages: ["対象のユーザーが見つかりませんでした。"]
              schema:
                $ref: "#/components/schemas/error" # 異常系モデルを参照する。


components フィールド (必須)

API スキーマなど、他の項目で共通して利用するものを定義する。

components:
  #===========================
  # callback キーの共通化
  #===========================
  callbacks: {}
  #===========================
  # link キーの共通化
  #===========================
  links: {}
  #===========================
  # response キーの共通化
  #===========================
  responses:
    unauthorized:
      description: Unauthorized レスポンス
      content:
        application/json: # MIME type
          foo: # ボディ例
            status: 401
            title: Unauthorized
            errors:
              messages: ["APIキーの認可に失敗しました。"]
          schema:
            $ref: "#/components/schemas/error" # 異常系モデルを参照する。
  #===========================
  # schema キーの共通化
  #===========================
  schemas:
    # ユーザー
    user:
      type: object
      properties:
        userId:
          type: string
        name:
          type: string
    # 正常系
    normal:
      type: object
      properties:
        userId:
          type: string
    # 異常系
    error:
      type: object
      properties:
        messages:
          type: array
          items:
            type: string
  #===========================
  # security フィールドの共通化
  #===========================
  securitySchemes:
    # ベーシック認証
    basicAuth:
      description: ベーシック認証
      type: http
      scheme: basic
    # Bearer 認証
    bearerAuth:
      description: Bearer認証
      type: http
      scheme: bearer
    # API キー認証
    apiKeyAuth:
      description: APIキー認証
      type: apiKey
      name: x-api-key # ヘッダ名は『x-api-key』とする。小文字が推奨である。
      in: header


security フィールド

components フィールドで定義した認証方法を宣言する。

ルートで宣言すると、すべてのパスに適用できる。

security:
  - apiKeyAuth: []


tags フィールド

各項目に付けるタグを定義する。

同名のタグをつけると、自動的にまとめられる。

tags:
  - name: ユーザー情報取得エンドポイント
    description: |

      ...


externalDocs フィールド

API を説明するドキュメントのリンクを定義する。

externalDocs:
  description: 補足情報はこちら
  url: https://foo.com


03. API スキーマ

API スキーマとは

RESTful-API が受信する CRUD のデータ型や必須データを定義したもの。受信したデータのバリデーションに使用する。


API スキーマによるバリデーション

データ型や必須データにより、リクエスト/レスポンスのデータのバリデーションを実行する。

例えば、API がレスポンス時に以下のような JSON 型データを返信する例を考える。

{
  "id": 1,
  "name": "Taro Yamada",
  "age": 10,
  "sports": ["soccer", "baseball"],
  "subjects": "math",
}

ここで、API スキーマを以下のように定義しておき、API からデータをレスポンスするときのバリデーションを実行する。

{
  "$schema": "https://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties":
    {
      "id": {"type": "integer", "minimum": 1},
      "name": {"type": "string"},
      "age": {"type": "integer", "minimum": 0},
      "sports": {"type": "array", "items": {"type": "string"}},
      "subjects": {"type": "string"},
    },
  "required": ["id"],
}