Classi developers Advent Calendar 2021の20日目の記事をご覧いただき,ありがとうございます!
サーバーサイドエンジニア@willsmileです.今年,子供が生まれたので,睡眠不足と戦っていて,個人の勉強時間を作ることがとても贅沢だと感じながら,この記事を書きました.
まえがき
一般的に,Opennessの観点から,APIを公開APIと非公開API(内部)と分類されます.前者は,より多くの開発者が利用できるように,APIドキュメントの整備がもちろんで,データモデル(RESTful APIの場合,リソース)から機能とその仕様も読み取れます.後者は,開発チームの規模とAPIの複雑性により,ドキュメントを記述する方式と範囲が定められます.極端の例を言うと,ソースコード自体をドキュメントの役割として働かせる開発チームはあります.そのため,
本記事は,非公開API(内部)のドキュメントに着目し,APIの仕様がソースコードのみで書き切れないことを前提として,OpenAPI(Swagger)を用いて,ドキュメントの作成時の留意点を紹介します.記事を読みやすくするため,OpenAPIの使い方の説明を割愛しますので,使い方自体を知りたい方はOpenAPIの公式ドキュメントを読むことをお勧めします.
本題
APIの設計が設計の成果物(データモデル,レスポンスのスキーマなど)と設計の意図という2つの部分から構成していると考えています.OpenAPIのような仕様(Specification)の記述方式が,設計の成果物を表現するために作られた道具であるので,その書き方にそって書けば,困ることが少ないかなと感じています.一方,設計の成果物と比べると,設計の意図を表現することが難しいと考えています.その難しさがあるから,設計の意図を可能な限り記述不要なレベル(成果物から意図を推測可能)までにシンプルすることがRESTful APIという規約のエッセンスのではないかと筆者が考えています.
しかし,そこまでにすることが恐らく理想の話にすぎません.なぜかをいうと,多くの開発現場では,変化し続けるニーズやユーザーからの要請に対応しなければいけません.そのために,要件・仕様を変更・修正し続けることが必要です.最初は,設計の成果物から設計の意図を推測できるとしても,改善の積み重ねとともに,両者の間にギャップが生じます.それを解消するために,設計の成果物だけではなくて,設計の意図をドキュメントに記述することが重要だと考えています.
これからは,以下の具体例(参考文献1に基づいて筆者が修正を加えた)を通じて,伝わるAPIドキュメントを作成する(特に,設計の意図の記述)ためのコツを3つ紹介します.
# pathsとcomponents以外の部分は省略します.
paths:
/books:
get:
summary: List books available in the book store for browsing, with filtering support to narrow the results
description: |
- Provides a paginated list of books based on the search criteria provided.
- If no search criteria is provided, books are returned in alphabetical order.
parameters:
- in: query
name: q
schema:
type: string
required: false
description: A query string to use for filtering books by title and description. If not provided, all available books will be listed. Note that the query argument 'q' is a common standard for general search queries
- in: query
name: offset
schema:
type: integer
default: 0
minimum: 0
required: false
description: A offset from which the list of books are retrieved, where an offset of 0 means the first page of results. Default is an offset of 0
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/ListBooksResponse'
components:
schemas:
ListBooksResponse:
description: |
A list of book summaries as a result of a list or filter request
type: object
properties:
books:
type: array
items:
$ref: '#/components/schemas/BookSummary'
扱う対象の範囲を明確にする
まず,具体例のsummary
の部分に着目します.
扱う対象としてのbooks
の修飾語の部分はavailable in the book store for browsing
と書かれています.ドメインエキスパートからみると,この部分が「当たり前だろう!」と思わるかもしれません.開発者にとっては,書かないと扱う対象の範囲が曖昧になり,チームで話す時に,認識に齟齬が生じる温床となっているので,明確に書いたほうがいいと考えています.
"空状態"という条件も忘れず
そして,具体例のdescription
の部分に着目します.
1行目の文Provides a paginated list of books based on the search criteria provided.
から,検索可能と結果のページネーション可能という設計の意図が把握できます.ただ,parameters
の部分を読んでみると,上記の記述がなくても,その意図をある程度推測できます.
2行目の文If no search criteria is provided, books are returned in alphabetical order.
は,検索のためのパラメータが何も付けていない場合,どのように結果を戻すのかを明確に記述しました.parameters
の部分を読んでみると,2つのパラメータのどれでも必須ではないこと,およびそれぞれが付いた場合と付いていない場合のAPIの振る舞いと読み取れますが,両方のどれでもが付いていない場合のAPIの振る舞いがいくつかの可能性があって,どれが選ばれたのかは,明確に書かないとがわからないことです.
"自体"を明確にする
最後は,具体例のcomponents
の部分に着目します.
OpenAPIにおいて,componentsがデータモデルを再利用できるように定義する目的で作られたが,筆者は意図を伝えるように,componentsを活用し,重要なものの"自体"(本質的にどの概念に該当するのか)を明確にすることを主張したいです.
例えば,具体例では,ListBooksResponseというcomponentが定義されました.仕様をまったく読まずに,GET /booksというエンドポイントだけで想像してみたら,Bookというリソースを取得することをイメージします.でも,実際に意図したことは,イメージと違っていて,Bookに関する要約(BookSummary)の配列を取得することです.このようなぱっと見る時の印象と実際に意図したことの間のギャップを解消する,あるいは最初から生じないために,具体例でのListBooksResponseのように,レスポンスの"自体"を明確にして,名付けることが有効のではないかと考えています.
あとがき
本記事は,伝わるAPIドキュメントを作成するためのコツを3つ紹介しました.現場経験が深い方からみると,当たり前のことばかりかもしれませんが,自分自身にとって,実践で気づいたことを一度言語化にして,良い学びの経験になると感じました.もちろん,記事ということが自己満に終わることがダメだと考えていて,より多くの人にとっても役に立つレベルに達すため,記事の内容について皆さんからのフィードバックを収集して改善していきたいと考えています.コメントやご意見があれば,ぜひコメント欄で教えてください!