[!] この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
みなさんはWebシステムのREST APIサーバーの開発の仕様検討について、その仕様の記述はどのように行っていますでしょうか?WikiとしてWebページに記載をしているチームや、テキストやExcelで自由記述の管理をしているチームもあると思われます。私たちのチームでは以前、ExcelによってAPI仕様の作成を行い、ファイルサーバーで管理を行っていました。 以前の管理方法ではいくつか不便な点がありました。
- バージョン管理が行いにくい
- 仕様の変更の際に差分が分かりにくい
- 各API間で共通となるパラメーターの管理がしにくい
- コードやテストの自動化がやりにくい
- フォーマットが自由記述になってしまい統一されない
これらの点の解消とより良いAPI仕様策定方法の確立のため、私たちのチームではOpenAPI Specificationによる仕様作成を始めたので、この記事ではOpenAPI Specificationの概要と実際の記述方法を例を交えて紹介していきます。
OpenAPI Specification概要
OpenAPI Specification(OAS)とは一般にOpenAPIと呼ばれ、REST APIの記述フォーマット(または記述)のことを指します。
WebシステムのAPI開発において、仕様を記述することができます。YAMLまたはJSONによる記述が可能であり、これらのフォーマットを知っている人にとっては比較的簡単に記述することができます。
OpenAPIとSwaggerの関係
OpenAPIを調べていくと、Swaggerというキーワードに出会います。
Swaggerとは2022年7月現在はOpenAPI記述のフォローやOpenAPIを利用して様々な働きをするオープンソースツールを指します。一方で以前はOpenAPIの前身であるSwagger Specification(Swagger仕様)として存在しており、現在のOpenAPIと同じ意味で使われていました。
Swaggerは2010年に始まり、その後SwaggerUI、Swagger Editor、Swagger Codegenなどのオープンソースツールも開発され、仕様とオープンソースツールで構成されるSwagger projectとして発展していきました。
2015年にSwagger projectはSmartBear Softwareに買収され、Swagger Specification(Swagger仕様)はLinux foundationに寄付されその名称をOpenAPI Specificationに変えて現在に至ります。現在OpenAPIは2021年に発表されたOpenAPI Specification 3.1.0が最新の安定バージョンになっています。
OpenAPIでできること
OpenAPIはフォーマットを学習することで、API仕様の記述スピードが上がり、記述の統一化が図れます。OpenAPIは基本的なAPI仕様の記述フォーマットの提供だけでなく、各種ツールを用いることで開発を行う上での様々なメリットを与えてくれます。例えば、記述したAPI仕様のドキュメント化、コードの自動生成、API仕様に沿って建てられたAPIサーバに対するテストなどを行うことができます。
各種ツールの紹介
先ほども紹介しましたが、OpenAPI仕様の作成や活用をサポートするツールとしてSwaggerツールという公式のオープンソースツールセットが存在します。
Swaggerツールには以下のものがあります。
- Swagger Editor
- Swagger UI
- Swagger Codegen
各ツールについて簡単に紹介していきます。
- Swagger Editor
- OpenAPI記述の作成時に利用するツールです。YAMLまたはJSONで記載します。特徴としてSwagger UIとともにブラウザ版が存在し、Swagger UIによってドキュメントを確認しながら作成が可能です。ブラウザ版のアクセス時にサンプルAPIが表示されるので、学習に役立ちます。
- Swagger UI
- OpenAPI記述をビジュアルドキュメントとして表示するオープンソースツールです。Swagger Editorとともにブラウザ版も用意されており、記述をドキュメント化したものを確認することが可能です。ブラウザ版のアクセス時にサンプルAPIが表示されるので、学習に役立ちます。
- Swagger Codegen
- OpenAPI記述からクライアント・サーバーのコードの自動生成をおこなってくれるツールです。C#やC++,Java,Python,Ruby,Typescriptといった様々な言語に対応しています。
またこれらのツールの他にも様々なツールが存在します。
- Stoplight Studio
- OpenAPIの記述エディターです。YAMLやJSONでの記述に加え、GUIでの記述が可能になっている特徴がありOpenAPIの学習に最適なツールです。商用利用可能であり、アプリの利用は無償で可能です。共有用にワークスペースの利用も可能ですが無償版では人数やプロジェクト数などの制限があります。有償版ではプランによってその制限を緩和できます。
- Swagger Viewer
- OpenAPI記述の作成時に利用するツールで、YAMLまたはJSONで記載したOpenAPIをドキュメント形式でプレビュー表示できます。Visual Studio Codeの拡張機能としても存在し、記載内容をドキュメントで確認しながら作成ができます。
- OpenAPI generator
- OpenAPI記述に対応したコード自動生成ツールです。Swagger CodegenのOpenAPI 3の対応遅れによりフォークされ、コミュニティ主導のツールとして誕生しました。Swagger Codegenと同じくオープンソースツールであり、2022年7月現在はどちらもOpenAPI 3に対応しているので、どちらを利用するかは生成コードを見るなどして選択すると良いと思います。
OpenAPIのバージョンについて
OpenAPIとして2022年7月現在一般的に利用されているメジャーバージョンはOpenAPI Specification 2,OpenAPI Specification 3です。OpenAPI Specification 2は2014年にリリースされたSwagger2.0を前身としているのに対し、OpenAPI Specification 3は2017年にOpenAPI3.0.0としてリリースされています。 違いとしては以下が挙げられています。
- バージョン表記がセマンティックバージョニングに従いmajor.minor.patchとなり、patchバージョンまで表すようになった
- componentsオブジェクトをドキュメント内の他の場所から参照される再利用可能なメタデータというグループ化を行い、以前までのdefenitionsやresponsesがまとめられた
- 複数ホストが記載可能になった
- exampleプロパティについてJSON文字列や外部ファイルの利用ができるようになった
これから作成するものについては、利用ツール等で特別制限がなければOpenAPI 3の利用が一般的です。
OpenAPIを利用するメリット・デメリット
OpenAPIを利用する上でのメリット・デメリットをまとめると以下のようになります。
- メリット
- 記述のアシストをしてくれるツールがそろっており、フォーマットの学習がしやすい環境が整っている
- 一度フォーマットに慣れてしまえば記述は楽になるうえ、ドキュメント化のツールを使用すればフォーマットを理解していない人でもAPIの仕様確認ができる
- 豊富なツールを使うことでコード生成やテストをOpenAPI記述をもとに行うことができる
- デメリット
- ツールによっては最新のOpenAPIに対応していないものも存在するので、ツール導入には注意が必要
- 最低限の学習コストがかかる
OpenAPIを導入するにあたっては学習コストがかかる面もありますが、一度フォーマットを習得してしまえばAPI仕様記述工程の効率化やチーム内での統一化が図れますし、YAMLやJSONで記述せず、GUIで作成できるツールも存在します。また、より学習コストはかかりますがOpenAPIを利用する豊富なツールを順次導入していくことで、コーディングやテスト工程の効率化も期待でき、結果的に開発全体の効率化が図れるものとなっています。
OpenAPIでの仕様作成(実践編)
実際に仕様作成の方法について、紹介します。
OpenAPI記述はYAMLまたはJSONで成り立っており、様々なオブジェクトを組み合わせることで構成されます。
それらのオブジェクトの組み合わせによって、REST API名やライセンス情報、連絡先情報、エンドポイントやそのオペレーション、各オペレーションのリクエスト・レスポンス情報といったAPI全体設計を記述することができます。
今回は、yaml形式で記述したOpenAPI Specification 3.1.0に基づいた説明を行います。
実際に記載した簡単な顧客情報管理システムのAPI仕様を例に説明していきます。 サンプルAPIサーバーの概要は以下です(簡易図)。
openapi: 3.1.0
info:
title: サンプル用顧客管理API
description: 顧客情報管理用のサンプルAPIサーバです。
termsOfService: https://example.com/terms/
contact:
name: API Support
url: https://example.com/support/
email: support@example.com
license:
name: ○○ License
url: https://example.com/licenses/LICENSE.html
version: 1.0.0
servers:
- url: https://samplehost/api/v1
description: 本番サーバ
- url: https://{name}:{port}/api/v1
description: 検証サーバ
variables:
name:
default: demo
description: 検証サーバ名
port:
default: '443'
description: 利用ポート番号
paths:
/customer:
parameters:
- schema:
type: string
in: header
name: Authorization
description: 認証トークン
required: true
get:
operationId: getCustomer
summary: 顧客情報の取得
description: 登録された顧客情報を取得します。
tags:
- Customer
responses:
'200':
description: |-
正常ルート
顧客情報のリストを取得します。
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'401':
description: 認証失敗
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: サーバの内部エラー
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
operationId: postCustomer
summary: 顧客情報の単数登録
description: 顧客情報を1件登録します。
tags:
- Customer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
responses:
'200':
description: |-
正常ルート
顧客情報の登録が成功した場合。
レスポンスには登録情報が含まれます
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'401':
description: 認証失敗
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: 内部エラー
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/customer/{id}:
parameters:
- schema:
type: integer
format: int32
name: id
in: path
required: true
description: 識別ID
- schema:
type: string
in: header
name: Authorization
description: 認証トークン
required: true
patch:
operationId: patchCustomer
summary: 顧客情報の単数更新
description: 顧客情報を1件更新します。
tags:
- Customer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
responses:
'200':
description: |-
正常ルート
顧客情報の登録が成功した場合。
レスポンスには登録情報が含まれます
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'401':
description: 認証失敗
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: 更新対象が存在しない
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: 内部エラー
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
operationId: deleteCustomer
summary: 顧客情報の単数削除
description: 顧客情報を1件削除します。
tags:
- Customer
responses:
'200':
description: |-
正常ルート
顧客情報の登録が成功した場合。
レスポンスには登録情報が含まれます
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'401':
description: 認証失敗
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: 削除対象が存在しない
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: 内部エラー
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
Customer:
title: 顧客情報
description: 顧客情報オブジェクト
type: object
required:
- name
- mailAddress
properties:
id:
description: 顧客識別ID
type: integer
format: int32
name:
description: 顧客名
type: string
age:
description: 年齢
type: integer
format: int32
mailAddress:
description: メールアドレス
type: string
job:
description: 職業
type: string
ErrorResponse:
title: エラーレスポンス
type: object
properties:
code:
description: エラーコード
type: string
message:
description: エラーメッセージ
type: string
まずは全体の構成です。構成としては下図のようになっています。
ここからはopenapi以外の各フィールドについて紹介していきます。
infoフィールド
info:
title: サンプル用顧客管理API
description: 顧客情報管理用のサンプルAPIサーバです。
termsOfService: https://example.com/terms/
contact:
name: API Support
url: https://example.com/support/
email: support@xxx.com
license:
name: ○○ License
url: https://example.com/licenses/LICENSE.html
version: 1.0.0
infoフィールドにはInfoオブジェクトを記載します。InfoオブジェクトはAPIに関する様々なメタデータを記述します。必須となるフィールドはtitle,versionです。
titleフィールドは記述したAPIのタイトルを記載します。versionフィールドは記述されたOpenAPIのドキュメントのバージョンを表しています。openapiオブジェクトのopenapiフィールドとは意味が異なる点に注意してください。その他のフィールドについて簡単に解説をすると、termsOfServiceは利用規約へのURLを表し、URL形式である必要があります。contactとlicenseはAPIの連絡先情報、ライセンス情報をそれぞれ表します。
serversフィールド
servers:
- url: https://samplehost/api/v1
description: 本番サーバ
- url: https://{name}:{port}/api/v1
description: 検証サーバ
variable:
name:
dafault: demo
description: 検証サーバ名
port:
default: 443
description: 利用ポート番号
serversフィールドにはServerオブジェクトが複数記載でき、Serverオブジェクトはサーバ情報を記載できます。
Serverオブジェクトはurlが必須フィールドになっており、ターゲットホストへのURLを記載します。{}で囲むことで変数の利用が可能です。descriotionには説明を記載します。variableはurlで変数を利用した場合の変数情報を記載します。Server Variableオブジェクトはdefault,enum,descriptionのフィールドを持ち、defaultフィールドは記載が必須になっています。
componentsフィールド
components:
schemas:
Customer:
title: 顧客情報
description: 顧客情報オブジェクト
type: object
required:
- name
- mailAddress
properties:
id:
description: 顧客識別ID
type: integer
format: int32
ComponentsフィールドにはComponentsオブジェクトを記載します。
ComponentsオブジェクトはOpenAPI Specificationを利用する上で再利用可能なオブジェクトのセットを記載します。ここに記載されたオブジェクトは参照されない限りはAPIの記述に影響を与えないので、明示的に参照する必要があります。今回例に用いているschemasフィールドにはSchemasオブジェクトを記載します。ここでは入力データや出力データを記載します。今回はリクエストとレスポンス(正常系)で利用する顧客情報(Customer)を再利用可能なものとして、componentsフィールドで定義しています。typeフィールドによってObjectであること、propatiesフィールドでプロパティ一覧、requiredフィールドで必須パラメータの指定を行っています。
pathsフィールド
paths:
/customer:
...
get:
operationId: getCustomer
summary: 顧客情報の取得
description: 登録された顧客情報を取得します。
tags:
- Customer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
responses:
'200':
description: |-
正常ルート
顧客情報のリストを取得します。
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
...
/customer/{id}:
parameters:
- schema:
type: integer
format: int32
name: id
in: path
required: true
description: 識別ID
- schema:
type: string
in: header
name: Authorization
description: 認証トークン
required: true
patch:
...
pathsフィールドではPathsオブジェクトを記載します。pathsオブジェクトには各エンドポイントと各オペレーションの相対パスを記載します。フィールド名は/{path}のような形式で記載しエンドポイントパスを表します。サーバーのベースURLにフィールド名を追加したものが完全なURLとなります。 {}をつけることでパスパラメーターを表し変数を利用することができます。
各フィールドの直下にはPath Itemオブジェクトを記載します。Path Itemオブジェクトには、APIのオペレーションを表すOperationオブジェクトとParameterオブジェクトを記載することができます。
Operationオブジェクトではgetやpostといった各操作の定義ができます。HTTPのPOSTメソッドを定義する場合は、postフィールドを用いることで表すことができます。
Parameterオブジェクトはパスで利用する全てのオペレーションに適用されるパラメータを定義できます。pathに変数を含めた場合にはここでパスパラメータの記載をします。ここでは認証トークンとURLパラメーターを指定しています。inフィールドにてパラメータの場所を指定できます。今回はpathに含まれるIDとheaderに含まれる認証トークンを記載しています。
Operationオブジェクトにも様々なフィールドが記載できます。operationIdは、オペレーションを識別するためのIDを設定可能です。このIDは記述するOpenAPI記述で一意である必要があります。responsesはオペレーションのレスポンス情報について記載します。requestBodyはオペレーションのリクエストボディ情報を記載します。またresponses,requestBodyで利用されている$refはコンポーネントの参照を表します。$refを用いることで、componentsフィールドで定義した再利用可能な様々なコンポーネントを利用することができます。 tagsはAPIドキュメントをグループ化するためのタグを設定することができ、parametersは各オペレーションで利用するパラメータを記載できます。Path Itemオブジェクトでparametersフィールドを設定している場合、同一名で記載することでオーバーライドすることも可能です。
ドキュメントサンプル
上記のAPIはSwagger Viewerでこのようなドキュメントとして確認できます。
各操作やcomponentsフィールドで設定したschemasについては折りたたまれており、開くことで記述した詳細な情報を見ることができます。 GETには、パラメーターにパスパラメーターやリクエストはないですが、PATCHにはパスパラメーターとリクエストボディの記載があります。 エラー時は顧客情報ではなくエラーレスポンスオブジェクトを指定しているので、そちらが反映されて見えます。各オブジェクトはSchema定義と例の両方が確認可能です。
おわりに
ここまで、OpenAPI Specificationの概要説明と作成部分を例を交えながら説明をしてきました。OpenAPIで得られるメリットはREST APIの仕様の記述の他にも関連ツールを用いた自動生成コードの作成やOpenAPI記述を用いたAPIサーバーのテストの自動化などにあります。現在私たちのチームでも仕様作成に加えて、これらのツールの導入を検討中です。この記事を読んでいる方のAPI仕様作成や管理方法の検討について少しでもお役に立てたら幸いです。
参考文献
- [1] Swagger公式 https://swagger.io/ ⧉
- [2] Swagger Editor GitHub https://github.com/swagger-api/swagger-editor ⧉
- [3] Swagger Codegen GitHub https://github.com/swagger-api/swagger-codegen ⧉
- [4] Stoplight Studio 公式 https://stoplight.io/studio ⧉
- [5] OpenAPI generator GitHub https://github.com/OpenAPITools/openapi-generator ⧉
- [6] OPENAPI INITIATIVE TDC: Structural Improvements: explaining the 3.0 spec(OpenAPI 3.0の説明) https://www.openapis.org/news/blogs/2016/10/tdc-structural-improvements-explaining-30-spec-part-2 ⧉
- [7] OpenAPI 3.1.0 GitHub https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md ⧉