メインコンテンツへスキップ
  1. 記事一覧/

APIは作る前に設計する:OpenAPIファーストの進め方

· loading · loading ·
仁才徳
著者
仁才徳
韓国ソウル在住のリーダー兼ソフトウェアエンジニア

先日、OpenAPIファースト開発について一般論を書きました。今回はその具体版です。うちのチームで試している計画ワークフローと、それに添えるYAMLの例を紹介します。

きっかけ
#

発端はレトロスペクティブでした。APIの計画がいかに見えていないか、フィードバックは容赦なかった。バックエンドは要件を自分たちなりに解釈してエンドポイントを作り、フロントエンドは後からレスポンスの形を知り、ズレは統合の段階、つまり一番高くつくタイミングで発覚する。そこで出た提案が、両サイドを計画段階から巻き込み、OpenAPIスキーマをすべての土台にしようというものでした。

プロセス
#

毎週、バックエンドとフロントエンドから1人ずつ、計2人の代表を選びます。代表の仕事は、スプリントの要件を理解して、計画用のOpenAPIスキーマをそれに合わせることです。2人でスキーマをレビューし、合っていない部分を直していけば、作り始める前に、双方が何を作るのかを正確に把握できます。

修正が落ち着いたら、スキーマをリポジトリにコミットします。するとCI/CDがそのyamlからモックAPIを生成します。スプリントが終わったら、次のスプリントのAPI要件をまとめた新しいyamlを切って、同じサイクルを繰り返します。

ツールまわりのメモ
#

  • yamlの可視化にはSwagger Editorで十分です。ファイルをアップロードすれば、ちゃんとしたSwaggerドキュメントとして表示されます。
  • このスキーマはあくまで計画用の成果物です。本物のAPIがデプロイされたら、各サービスが自分のopenapi.jsonを生成するべきで、それをダウンロードして次の計画に再利用します。
  • CI/CDパイプラインには、yamlを受け取ってモックサーバーを立てる仕組みが必要です。openapi-mockは有力な選択肢だと思います。

なぜやる価値があるのか
#

議論が一番安い場所に移動するからです。スキーマを書いている段階でフロントとバックのエンジニアが同席していれば、誤解はコードになる前に捕まります。API要件はスプリント途中で再交渉されるのではなく一度で合意されるし、モックサーバーのおかげでフロントエンドは待たずにすぐ着手できる。重要な細部の見落としもぐっと減ります。なにしろ2組の目と、書面になった契約をすり抜けなければならないので。

革命的な話はひとつもありません。ただ、機械がモックとして返せるくらい具体的なところまで計画を落とし込んだだけです。そして結局のところ、計画に必要な具体性とは、ちょうどそのくらいなのだと思います。

付録A - yamlの例
#

openapi: 3.1.0
info:
  title: PKY Sprint 3
  description: |-
    PKY Sprint 3 Development Schema
  termsOfService:
  contact:
    email: jared@lynskey.co.nz
  license:
    name: None
    url:
  version: 1.0.11
externalDocs:
  description:
  url:
servers:
  - url: https://api-mock.com/api/v3
tags:
  - name: curation
    description: PKY-1081 Curated Lists Feature
    externalDocs:
      description: Jira Epic
      url: https://pickydev.atlassian.net/browse/PKY-1081
paths:
  /curations:
    get:
      tags:
        - curation
      summary: Return the curations created by users
      description: Returns a map of status codes to quantities
      operationId: getCurations
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: integer
                  format: int32
      security:
        - api_key: []
  /curations/{curationId}:
    get:
      tags:
        - curation
      summary: Find purchase order by ID
      description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
      operationId: getOrderById
      parameters:
        - name: orderId
          in: path
          description: ID of order that needs to be fetched
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
            application/xml:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          description: Invalid ID supplied
        '404':
          description: Order not found
    delete:
      tags:
        - curation
      summary: Delete purchase order by ID
      description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
      operationId: deleteOrder
      parameters:
        - name: orderId
          in: path
          description: ID of the order that needs to be deleted
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '400':
          description: Invalid ID supplied
        '404':
          description: Order not found
components:
  schemas:
    Curation:
      type: object
      properties:
        id:
          type: integer
          format: int64
          examples: [10]
        curationId:
          type: integer
          format: int64
          examples: [198772]
        quantity:
          type: integer
          format: int32
          examples: [7]
        status:
          type: string
          description: Order Status
          examples: [approved]
          enum:
            - placed
            - approved
            - delivered
        complete:
          type: boolean
      xml:
        name: order
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
      xml:
        name: '##default'
  requestBodies:
    Curation:
      description: Curation object that needs to be added
      content:
        application/json:
          schema:
            $ref:
        application/xml:
          schema:
            $ref:
  securitySchemes:
    petstore_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl:
          scopes:
            write:curations: modify curations in your account
            read:curations: read your curations
    api_key:
      type: apiKey
      name: api_key
      in: header