先日、OpenAPIによるAPIファースト開発について一般論を書きました。今回はその具体版です。うちのチームで試している計画ワークフローと、それに添えるYAMLの例を紹介します。
先に要点をまとめると、このデザインファーストのプロセスはスプリントごとに次の流れで回します。
- そのスプリントのAPI作業の担当を、バックエンドとフロントエンドから1人ずつ決める。
- 2人でスプリントの要件に沿ってOpenAPIスキーマを書く(または更新する)。双方が納得するまでレビューする。
- スキーマをリポジトリにコミットする。ここから先は、これがコントラクトになる。
- コミットされたスキーマから、CIがモックサーバーを立てる。
- フロントエンドはモックに対して開発し、その間にバックエンドが本物のエンドポイントを実装する。
- 本物のサービスがデプロイされたら、そこから生成されたスペックを次のスプリントのスキーマの出発点にする。
きっかけ#
発端はレトロスペクティブでした。APIの計画がいかに見えていないか、フィードバックは容赦なかった。バックエンドは要件を自分たちなりに解釈してエンドポイントを作り、フロントエンドは後からレスポンスの形を知り、ズレは統合の段階、つまり一番高くつくタイミングで発覚する。そこで出た提案が、両サイドを計画段階から巻き込み、OpenAPIスキーマをすべての土台にしようというものでした。
プロセス#
毎週、バックエンドとフロントエンドから1人ずつ、計2人の代表を選びます。代表の仕事は、スプリントの要件を理解して、計画用のOpenAPIスキーマをそれに合わせることです。2人でスキーマをレビューし、合っていない部分を直していけば、作り始める前に、双方が何を作るのかを正確に把握できます。
修正が落ち着いたら、スキーマをリポジトリにコミットします。するとCI/CDがそのyamlからモックAPIを生成します。スプリントが終わったら、次のスプリントのAPI要件をまとめた新しいyamlを切って、同じサイクルを繰り返します。
ツールまわりのメモ#
yamlの可視化にはSwagger Editorで十分です。ファイルをアップロードすれば、ちゃんとしたSwaggerドキュメントとして表示されます。
CI/CDパイプラインには、yamlを受け取ってモックサーバーを立てる仕組みが必要です。StoplightのPrismは広く使われている選択肢で、OpenAPI 3.1にも対応しています。
npm install -g @stoplight/prism-cli
prism mock openapi.yamlこれでスキーマ内のすべてのパスが http://127.0.0.1:4010 で提供され、スキーマの examples がそのままレスポンスとして返ってきます。たとえば GET /lists/10 なら、付録のサンプルのリストが返ります。コントラクトもきちんと守らせてくれます。例ではAPIキーを宣言しているので、X-API-Key ヘッダーのないリクエストには401が返ります。フロントエンドが作り込むべきなのは、まさにこの挙動です。コンテナの中で動かすときは、外からアクセスできるように -h 0.0.0.0 を付けてください。openapi-mockは、ランダムなデータでレスポンスを埋めてくれる、より軽量な選択肢です。
モックを立てる前にスキーマをリントしておくのもおすすめです。壊れたコントラクトがあればビルドが失敗するので、フロントエンドが混乱せずに済みます。Spectralは、1行のルールセットを置けば動きます。
npm install -g @stoplight/spectral-cli
echo 'extends: ["spectral:oas"]' > .spectral.yaml
spectral lint openapi.yaml最後に、このスキーマはあくまで計画用の成果物です。本物のAPIがデプロイされたら、各サービスが自分のopenapi.jsonを生成するべきで、それをダウンロードして次の計画に再利用します。
なぜやる価値があるのか#
議論が一番安い場所に移動するからです。スキーマを書いている段階でフロントとバックのエンジニアが同席していれば、誤解はコードになる前に捕まります。API要件はスプリント途中で再交渉されるのではなく一度で合意されるし、モックサーバーのおかげでフロントエンドは待たずにすぐ着手できる。重要な細部の見落としもぐっと減ります。なにしろ2組の目と、書面になった契約をすり抜けなければならないので。
革命的な話はひとつもありません。ただ、機械がモックとして返せるくらい具体的なところまで計画を落とし込んだだけです。そして結局のところ、計画に必要な具体性とは、ちょうどそのくらいなのだと思います。
付録A - yamlの例#
openapi: 3.1.0
info:
title: MyApp Sprint 3
description: Planning schema for sprint 3. Drives the mock server only.
version: 0.3.0
servers:
- url: https://api-mock.example.com/v1
tags:
- name: lists
description: Saved lists feature (PROJ-123)
security:
- api_key: []
paths:
/lists:
get:
tags: [lists]
summary: Return the current user's saved lists
operationId: getLists
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/List'
/lists/{listId}:
get:
tags: [lists]
summary: Find a list by ID
operationId: getListById
parameters:
- name: listId
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/List'
'404':
description: List not found
components:
schemas:
List:
type: object
required: [id, title]
properties:
id:
type: integer
format: int64
examples: [10]
title:
type: string
examples: [Weekend reading]
itemCount:
type: integer
examples: [7]
securitySchemes:
api_key:
type: apiKey
name: X-API-Key
in: header