こんにちは、freee API開発チームのkotegawaです。 この記事はfreee Developers Advent Calender 2018の19日目の記事です。
freeeのAPI開発
freeeでは今年の7月から、API開発専任チームが新設されました。 API開発チームでは、
- 社外向け新規APIの開発
- API開発基盤の強化
- 新規プロダクトの開発
などの業務を社内の認証基盤チームやインフラチームと協力しながら行っています。 今日はAPIチーム発足前にfreeeが抱えていたAPIの課題と、それを解決するために導入したスキーマ駆動開発について紹介します。
APIチーム誕生前の課題
もともと弊社のAPIの課題として、以下のようなものがありました。
- APIドキュメントと実際の実装に差異があった
- 開発する度にAPIドキュメントを更新することにメンテナンスコストがかかっており、また手動で更新するオペレーションをとっていたことで、ミスも生まれやすい環境にあった。
- デグレ問題
- 社外公開するAPIと社内向けのprivate APIで共通のロジックを使っている箇所が多く、private APIへの変更が、社外APIへの予期せぬデグレ・バグや事前告知のない変更を生むことがあった。
どうやって解決したか
上で挙げた課題を解決するために、APIチームではSwagger(Open API)を用いた スキーマ駆動開発 という手法を取り入れました。
その内容としては、APIスキーマ(仕様書)をjsonやらyamlやらで記述していけば、APIドキュメントやリクエストのvalidation、E2Eテストがスキーマに基づいて自動生成されるような環境を目指していくというものです。
実際にスキーマ駆動開発をどのような技術で実現していったか、以下で解説していきます。
Swagger(Open API)
Swaggerは正式にはOpen APIという名前になってますが、RestfulなAPIの仕様策定・実装するためのオープンソースのフレームワークです。
画面左側のようにjsonやyamlでSwagger Specという仕様に基づいてスキーマを記述していけば、その周辺ツール群がよしなにAPI開発に必要なものを自動生成してくれます。 Swagger公式ツール群の例としては、以下のようなものがあります。
- 上記画像の右側のようなAPIドキュメントを自動生成してくれるSwagger UI
- APIクライアントやmockサーバーを自動生成してくれるSwagger Codegen
committee
Swagger周辺のツール群には上記の公式のもの以外にも多くのがあります。その1つがcommitteeというgemです。
committeeによるRequest Validation
committeeはRackのミドルウェアで動作するgemで、Swagger Specに基づいて記述されたjsonを噛ませてあげれば、そのスキーマに基づいて、公開APIへのリクエストに対してvalidationをかけてくれます。
例えばschema jsonに以下のような定義をすると、
{ "name": "walletable_type", "required": true "type": "string", "enum": [ "bank_account", "credit_card", "wallet" ], "description": "口座区分 (銀行口座: bank_account, クレジットカード: credit_card, 現金: wallet)" }
まずは上記が反映されたAPIドキュメントが自動生成され、
定義した型やenumに反したリクエストを送ると、committeeがRackの層で検知して、↓こんな感じでerror responseを返してくれます。
[ 必須parameter未指定の場合 ]
{ "status_code": 400, "errors": [ { "type": "validation", "messages": [ "walletable_type が指定されていません。" ] } ] }
[ 指定した型定義やenunの定義から外れたparameterが来た場合 ]
{ "status_code": 400, "errors": [ { "type": "validation", "messages": [ "walletable_type は string で指定してください。", "walletable_type は [bank_account, credit_card, walle] のいずれかを指定してください。", ] } ] }
ただし、committeeがやってくれるのはRackの層で例外を吐いて英語の文字列のエラーを返してくれるだけです。(エラーの種類にかかわらず、raiseするのは単一の例外クラスでmessageが違うだけという、ちょっと不親切な感じ。)
Rack層の例外を拾う仕組みや、英文のエラーをパース・構造化して、意味がわかる形でユーザーに返すロジックは別途実装が必要になりました。
committeeによるResponse Validation
committeeにはリクエストの制御だけでなく、レスポンスの制御も可能です。
具体的な実装としては、RailsのE2E(Requet Spec)で用いる get
put
等のメソッドをオーバーライドして、committeeのチェックを噛ませることによって、現在返しているレスポンスがスキーマで定義しているものと相違ないかを確認しています。
現在はこのチェックをCIに組み込んで、APIのレスポンスに影響があるような変更があった場合は検知できるような仕組みにしています。
Swagger, committeeを導入してよかったこと
APIの品質
リクエストを動的に、レスポンスを静的にスキーマと相違ないかチェックできるようにできるようにしたことによって、
- APIドキュメントと実装の差異
- APIレスポンスへの意図せぬデグレ
といった冒頭で挙げた問題を自動で防げる仕組みをつくることができました。
また、スキーマから吐き出したAPIドキュメントを事前にQAチームに共有しておくことで、効率的にQAテストを進めることができるようになりました。
APIの開発スピード
開発スピードの観点から見ても、以下のようなメリットがありました。
- APIを新規開発する際に、スキーマから記述することで、仕様書としてPMにレビューを依頼できる
- 今まで個別のendpointごとに記述しなければならなかったvalidationが大幅に減った
- responseの中身をチェックするテストを手動で書いていく必要がなくなった
- 今までは
expect(json['id']).to eq 1
といった感じ、一つ一つのフィールドの値チェックをするために膨大な量のテストを書いていました。
- 今までは
このように、APIを品質・開発スピードの両面で改善ができたので、Swagger, committeeの導入はやってよかったなと思っています。
新たに生まれた辛み
一方で今の運用に何の不自由も感じていないわけではありません。
50を超えるエンドポイントの詳細を1つのjsonファイルに記述していく大変さはやはりあります。 しみじみと思うのが、jsonはやはり基本的に人間が手書きするのに適した形式ではないということです。
このような問題に対して考えている今後の解決策は、
- スキーマをyamlで記述していく
- Swaggerはyamlでも記述できますが、yamlもyamlでネストが深くなると辛みも深くなるので根本的な解決にはならない
- jsonファイルの分割
- jsonを書く業から逃れられるわけじゃないので、これも根本的な解決にはならない
- Protobufでデータ構造を定義して、jsonにシリアライズする
- protoc-gen-swaggerというプラグインを活用すれば、Swaggerに対応した形式のjsonファイルも吐き出せる
将来的には「Protobufでスキーマを記述してSwagger形式のjsonにシリアライズ」というよりは、REST APIからgRPCに移行して今のSwaggerツール群がやってくれてることをまるっとProtobufに任せるというのもできればいいなという気持ちがあります。 ただgRPCの普及度を見るとさすがに時期尚早感が強いなと思っています。
freee APIの今後について
APIのさらなる拡充をしていくと同時に、APIチームから新プロダクトもリリース予定です。(正式発表はまた後日ということになっているので、お楽しみに。)
その他にも、
- 多言語対応APIクライアント
- Webhook API
- GraphQL対応
- AWS lambdaによる実行環境の提供
等々、夢のあるバックログがたくさん積まれています。
API開発チームでは、もっと多くの方に快適にAPIを使っていただけるよう、これからも邁進していきます。是非freeeのAPIを試してみてください。
freee Developers Community | freee Developers Community
また、一緒にAPIを開発していってくれるエンジニアも募集中です。ご興味あれば、ご連絡ください。
明日は関西のたこ焼きチームから大きな存在感を出しているyoshiさんの記事です。お楽しみに!