Open API はじめました
始めに
こんにちは。株式会社ペライチ のサーバーサイドエンジニアの永見です。
開発プロセスに Open API を導入しました。ここにその知見を書いておきます。
背景
ペライチではメインのサイト作成サービスの他に、予約や顧客管理などさまざまなサービスを扱っています。各サブサービスはマイクロサービス化しており(全部ではないけど)、Web API サーバとして稼働しています。
開発の課題として以下がありました。
-
課題 1 : 実装とドキュメントが一致していない
API の設計を Git 外のドキュメントに記載していた
まったく記載がないよりはマシですが、どうしてもメンテナンスが後手になり、
実装と乖離している状態が定常化していました。 -
課題 2 : フロントエンドとバックエンドの連携
開発メンバーが増え、メンバー一人でバックとフロントの両方を受けもつことが少なくなり、
フロントエンドとバックエンドの分野に、それぞれ担当範囲が分かれてきました。
その結果、バックエンド側の API 実装をフロントエンド側で待つ場面が多くなりました。
また、細かい部分で認識齟齬が起きていたりしました。 -
課題 3 : API の数が多くなった
大規模なプロジェクトが立ち上がったこともあり、全体の API の把握が難しくなっていました。
これらを解決するため、 Open API の導入を進めることにしました。
Open API とは
一言でいうなら、Web API のインタフェースを定義したファイルまたはその記法でしょうか。
誤解を生むのはマズイので公式ページの紹介文を持ってきました。
The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
An OpenAPI definition can then be used by documentation generation tools to display the API, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases.
OpenAPI 仕様 (OAS) は、言語に依存しない標準の RESTful API へのインターフェイスを定義します。これにより、人間とコンピューターの両方が、ソース コードやドキュメントにアクセスしたり、ネットワーク トラフィックの検査を行ったりすることなく、サービスの機能を発見して理解できるようになります。適切に定義されていれば、コンシューマは最小限の実装ロジックでリモートサービスを理解し、対話することができます。
その後、OpenAPI 定義は、API を表示するためのドキュメント生成ツール、さまざまなプログラミング言語でサーバーとクライアントを生成するためのコード生成ツール、テスト ツール、およびその他の多くのユースケースで使用できます。
- 言語に依存しない
- RESTful API インタフェースの定義
- 人間とコンピュータの両方が読める
- 定義を元にさまざまなツールで使える
あたりがポイントですね。
Swagger との違い
Open API
を調べていくと Swagger
を目にするはずです。
- 元は Swagger という名のオープンソースのプロジェクト。
- SmatrBear Software 社が買収する。
- RESTful API の記述標準化を目指す団体 Open API Initiative が設立される。
Microsoft, Google, IBM などが発足。 - SmatrBear Software 社が Swagger 2.0 を団体に寄贈し、ベースの仕様になる。
-
Swagger
からOpen API
に名称が変更される。
ですので、最新の仕様は Open API
になります。
Swagger
は当初から作られたツール群に名前が残っています。
使い方
API 定義ファイルを用意する。
MUST なのはこれだけです。
形式は JSON を YAML の両方があります。
私は直接書いてしまってますが、ツールから新規作成するのもよいでしょう。
実際、何人かの開発メンバーはツールオンリーで進めています。
Open API 関連のツールは数多くありますが、
対応しているバージョン、形式で差異があるため、
使う環境やルールは、ツールによって決めることになります。
方針
-
バージョンは Open API 3.0.3 とする (最新は 3.1)
-
YAML 形式で書く
-
定義ファイルは API サーバのリポジトリに置く
-
定義ファイルはファイル分割をしない
-
既存の API は REST ルールにのっとっていなくても現状の通りに書く
新規作成する場合にはなるべく REST のルールにしたがって設計する。
ツール
-
Stoplight Studio (エディタ/プレビュー/API 実行/エラーチェク/モック)
多機能な便利なツール。
下記の URL からダウンロードします。
ちなみにスポットライトに見えますが、ストップライトです。綴り間違いではありません。 -
Swagger Editor (エディタ/プレビュー/API 実行/エラーチェク/クライアント作成)
Docker で起動する方法
docker pull swaggerapi/swagger-editor docker run -d -p 80:8080 swaggerapi/swagger-editor
基本部分だけなら VS Code のプラグイン
OpenAPI (Swagger) Editor
がお手軽です。
https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi -
Committee (テスト)
Rails で API 定義をもとに RSpec でリクエストパラメータ、レスポンスの検証を行える Gem です。
Gemfile
gem 'committee-rails'
書き方
公式の GitHub リポジトリにあるドキュメント ( 3.0.3.md )が読みやすく、よく参照していました。
例を一度提示します。
openapi: 3.0.3
info:
title: Exsample API
description: サンプル API
version: 1.0.0
contact:
name: Exsample Developent
email: dev@example.com
servers:
- url: 'http://localhost:88'
description: 開発環境
paths:
'/api/v1/users/{user_id}':
get:
description: ユーザ詳細
operationId: UserShow
parameters:
- name: user_id
in: path
required: true
schema:
type: integer
description: ユーザID
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
age:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
put:
description: ユーザ更新
operationId: UserUpdate
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: ユーザID
requestBody:
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
age:
type: integer
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
age:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
基本フォーマット
最初の階層に大項目を並べます。
openapi: 3.0.3 # [必須] Open API バージョンを指定します
info: # [必須] API 定義の基本情報を記載します
...
servers: # API サーバの情報を記載します
...
paths: # [必須] エンドポイントのリクエストやレスポンスを記載する。メインとなる部分
...
components: # 共通部分をここにまとめておきます
...
paths
paths
'/api/v1/users/{user_id}/messages': # [必須] エンドポイントのURL。動的な箇所は {} 囲みでパラメータ名を入れます
post: # [必須] HTTP メソッド名をつけます。get, post, put, delete など
description: メッセージ作成
operationId: MessageCreate # [推奨] ツールが判別に使ってます。必須としているツールが多いです
parameters: # パスやクエリやヘッダは parameters の項で指定します
- name: user_id # [必須] パラメータ名
in: path # [必須] パラメータの種類。path, query, header, cookie など
required: true
schema:
type: integer
description: ユーザID
requestBody: # POST や PUT メソッドで入れるリクエストボディはこちら
content: # [必須] requestBody の場合は必須
application/json: # [必須] Content-type を指定
schema: # [必須] この下の階層にスキーマを定義します。schema の項を参照。
type: object
required:
- message
properties:
message:
type: string
responses: # [必須] レスポンスを定義
'200': # [必須] HTTP ステータスコードを指定します。指定以外の場合は default を使います
description: 成功時
content: # [必須] responseの の場合は必須
application/json: # [必須] Content-type を指定
schema: # [必須] この下の階層にスキーマを定義します。schema の項を参照。
type: object
properties:
result:
type: string
schema
たいていの API の場合は、最初に Object 型か Array 型を指定することが多いでしょう。
schema:
type: object
required: # 必須とするプロパティを配列で指定。この必須はキーの存在を見ます。値が空かどうかは見ません。
- id
properties: # [必須] object 型の場合は必須
id: # プロパティ名
type: integer # データ型
schema:
type: array
items: # [必須] array 型の場合は必須
type: integer # データ型 この場合は [1, 2, 3] のような配列。上記の object 型の指定もできます
データ型は integer/number/string/boolean + object/array のみ指定できます。
integer や string は format
で書式を指定できます。
id:
type: integer
uuid:
type: integer
format: uuid
type | format | 説明 |
---|---|---|
integer | INT型 | |
integer | int32 | 符号付き32ビット INT型 |
integer | int64 | 符号付き32ビット LONG INT型 |
number | float | 浮動点少数 FLOAT型 |
number | double | 浮動点少数 DOUBLE型 |
string | 文字列 | |
string | uuid | UUID形式の文字列 |
string | メールアドレス書式の文字列 | |
string | byte | base64エンコードした文字列 |
string | binary | バイナリ |
boolean | ブール型 | |
string | date | 日付型 |
string | date-time | 日時型 |
string | password | パスワード |
他のプロパティも用意されています。
prefecture:
type: string
example: '東京都' # 例となる値を示す
phone_number:
type: string
pattern: '^[0+]\d{8,10}$' # フォーマットを正規表現で指定する
nullable: true # NULLを許可する
birth_year:
type: integer
minimum: 1900 # 最小値を指定する
maximum: 2100 # 最大値を指定する
components
components 項目に共通項をまとめ、$ref
で参照することで記載を省略できます。
openapi: 3.0.3
info:
title: Exsample API
description: サンプル API
version: 1.0.0
contact:
name: Exsample Developent
email: dev@example.com
servers:
- url: 'http://localhost:88'
description: 開発環境
paths:
'/api/v1/users/{user_id}':
get:
description: ユーザ詳細
operationId: UserShow
parameters:
- $ref: '#/components/parameters/PathUserId'
responses:
'200':
description: 成功時
content:
application/json:
schema:
$ref: '#/components/schemas/User'
put:
description: ユーザ更新
operationId: UserUpdate
parameters:
- $ref: '#/components/parameters/PathUserId'
requestBody:
$ref: '#/components/requestBodies/User'
responses:
'200':
description: 成功時
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'/api/v1/users': # POSTを追加でもシンプルに書けます
post:
description: ユーザ作成
operationId: UserCreate
requestBody:
$ref: '#/components/requestBodies/User'
responses:
'200':
description: 成功時
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
parameters:
PathUserId:
name: user_id
in: path
required: true
schema:
type: integer
description: ユーザID
requestBodies:
User:
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
age:
type: integer
schemas:
User:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
age:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Good & More
Good
-
ドキュメントに書く手間がほぼなくなった
ツールに強力なプレビュー機能がついているので、API ドキュメントとして扱えます。
API 定義とテストを連動させることで、実装との乖離も少なくできました。 -
リクエストやレスポンスのテストがシンプル
Committee を使うと Rails のテストで
データ型の細かい検証は、下記の 2 行で済みます。context 'when success' do it 'return expected body schema' do assert_request_schema_confirm # リクエストの検証 assert_response_schema_confirm 200 # レスポンスの検証 end end
-
現状の Web API の問題点が見える
Open API が RESTful ルールであることを想定しており、
API 定義を書くことで、 API の課題が見えてきました。- リクエストとレスポンスで同じ項目名でも型が異なる
- データタイプに依存してリクエストやレスポンスが変わる
- 複数のデータ型があるプロパティ
これらのケースは一応、書く方法はありますが、かなりやりずらさを感じました。
フロント側でも TypeScript で型定義しにくい面もあります。設計を見直すべき箇所です。
More
-
スキーマ駆動開発を進めるには?
API 定義を書いてから実装を進める開発手法ですが、- Open API に対するメンバーの慣れが必要。
- モックサーバとして動かすには example を充実させた方がよい。
モックツールはデータ型に応じて、0
やstring
を入れて返してくれますが,
フロント側の開発に使うには、ちょっと扱いにくい...。
必須でなくてもスキーマの定義の example を書いた方が良いですね。
-
ツールからの API 実行について。
- サーバ側の CORS 設定が必要。
API 実行でレスポンスが返ってこず、気付くまでに結構ハマりました...。
- サーバ側の CORS 設定が必要。
まとめ
当初は難しそうに見えましたが、Open API の導入自体は意外と簡単でした。書き方も慣れれば特に問題ありません。
その先の、どうアプリケーションに連動させるか、開発プロセスにどう組み込むかで悩む時間の方が多かったです。
API 定義からクライアント側ソースコード、 Wiki ドキュメントを自動作成する高度なツールもあります。
使いこなすほど効率化できるので、開発のなるべく早い段階で入れることをお勧めします!
採用情報
現在エンジニア募集しています!
▼ 採用ページ
▼ 選考をご希望の方はこちら(募集職種一覧)
▼ まずはカジュアル面談をご希望の方はこちら
募集中の職種についてご興味がある方は、お気軽にお申し込みください(CTO がお会いします)
Discussion
書き方の項目部分にtypoがあります。
interger
-->integer
>いもけんぴさん
ご指摘ありがとうございます。
遅くなりましたが修正しました!