こんにちは、freeeのPublic APIチームでエンジニアをしているまっつーです
花粉症ですごい鼻水が出るので少しくらい体重落ちてるんじゃないかと期待してます
去年の6月15日、会計freeeのPublic APIは新バージョンを公開しました
この新バージョンでは約30個の破壊的変更を含んでいます
そして去年の12月、会計freeeのPublic APIは半年間の並行運用期間を経て、新バージョンへの完全移行を達成しました
この記事では後方互換性を保ち、既存ユーザーに影響を与えないことと、APIの負債を解消しより使いやすいAPIへと進化させることを両立するために、どのように工夫して進めたのかをお伝えしたいと思います
破壊的変更とは
Public APIはそれを使って開発や業務を行っている方がいるため、変更する時には後方互換性を担保しすべての利用者の動作に影響がないように行わなければなりません
しかし、会計freeeの機能拡充による変化や、過去に作った仕様が今現在では最適なものとは言えなくなくってしまったなどの理由から、後方互換性を担保しない変更を行わざるを得ない場合があります
freeeの場合はPublic APIを利用してアプリを作成しfreeeアプリストアに公開している開発者もいるので、後方互換性がない (今まで通りのリクエストを送信しても返ってくるレスポンスが異なる)ということは公開しているアプリが突然動かなくなるという事態につながります
この後方互換性を担保しない変更を破壊的変更と呼び、破壊的変更を実現するためにPublic APIでは並行運用期間を設けるという方法が取られることが多いです
また、上で述べたようにPublic APIはそれを使ってアプリを公開している開発者もいるので、新バージョンの公開には外部との連絡、連携が必須です
開発者には並行運用期間中に、利用しているAPIのバージョン移行をしてもらう必要があります
外部とのコミュニーケーションについては同じAPIチームでディベロッパーリレーションを担当しているニックさんが記事を書いてくれているので、ぜひそちらも読んでみてください
https://developers.freee.co.jp/entry/how-to-make-a-communication-plan
新バージョン移行への流れ
2020年1月頃: 変更点のスコープ整理とversioningの方法の検討
新バージョン公開の半年前からプロジェクトはスタートしました
目標を設定する
まず、今回の新バージョン公開で実現したいこと (目標)を決めました
以下の2点です
Public API Releaseからここまでのフィードバックおよび利用しにくい個所の改善(レスポンスの構造が他と異なる、freeeの権限がうまく効いていないAPIの改修など)を行い、開発者が速く安く手戻りなく作れるおよび使いやすいAPIになっている
インパクトを受けないアプリは改修アクションを行わなくても新しいバージョンに移行ができる(特定の日をもって自動切り替え)
スコープを整理する
次に新バージョンに盛り込みたい破壊的変更リストの整理を行いました
それまでユーザーからフィードバックをもらっていたものをメインに、具体的にどのエンドポイントのどこをどう直すかという点を確定していきました
新バージョンの表現方法、指定方法を決める
新バージョンを公開するにあたって
- バージョニングポリシーをどうするか
- バージョン指定方法をどうするか
という問題がありました
前提として、それまで会計freeeのPublic APIはバージョンいくつとはリファレンス上で明記しておらず、pathは api/1/{リソース名} となっていて「バージョン1 っぽいけどどこにも書いていない」という状態でした
バージョニングポリシーをどうするか
案1 incrementalに増やしていき、次の新バージョンをv2とする
- 現在 api/1 というpathになっているので旧バージョンをv1、新バージョンをv2、今後リリースされるものはv3とincrementalに増やしていく案
- TwitterのAPIはこの手法をとっている
- 良い点: 直感的にわかりやすい
- 悪い点: バージョンの指定方法についての部分でも触れるが、バージョン指定方法をpath以外にした時、pathは /1 だけどバージョンはv2という状況がおこる。一方バージョン指定方法をpathで指定する (新バージョンのpathを /api/2 とする)と全てのAPIが新バージョン移行対象となり、既存で動いている全アプリでエンドポイント移行が必要になり、目標の2つ目が達成できなくなる
案2 日付で表現
- 新バージョンのリリース日をバージョン名とする案
- StripeのAPIはこの手法を取っている
- 良い点: pathが api/1 の状態を維持したまま新しいバージョンのリリースが出来る
- 悪い点: 良い点と表裏一体だが、バージョン指定方法をpath以外にした場合、api/1 というpathのまま今後バージョンを上げていくことになる
バージョン指定方法をどうするか
案1 pathで表現
- 現在 api/1 となっている1の部分を新しいバージョン名に変える案
- 良い点: ユーザーからするとどのバージョンのAPIを使っているかわかりやすい
- 悪い点: 新バージョンで変更が加わるAPIを使っていないユーザーにも影響がある。内部的にも仕様変更がない部分は別々のパスからくるリクエストを同じコードで処理するなど複雑さが増す
案2 アプリ管理画面から指定
- freeeのAPIはアプリを作成してアクセストークンを取るので、アプリ管理画面で利用するAPIのバージョンを切り替える案
- 良い点: 画面から決めることが出来るので設定が容易
- 悪い点: UIが必要なので他の案より工数が多くかかる
案3 ヘッダーで指定
- 新バージョンを識別する
X-Api-Version
のようなカスタムヘッダーを用意して、ヘッダーがある時は新バージョン、ない時は旧バージョンの動きをする案 - 良い点: 実装は比較的容易
- 悪い点: バージョンの指定に多少の技術的なスキルが必要。freeeのAPI利用ユーザーにはエンジニアでは無い会計士さんのような方もいるのでこの点は意識して議論しました
- 新バージョンを識別する
結論 目標を考慮して、インパクトを受けないアプリは改修アクションを行わなくても新しいバージョンに移行ができるために、バージョニングポリシーについては案2の日付でのバージョン表現、バージョン指定方法は案3のヘッダー指定にし、リリース後様子を見てアプリ管理画面での指定の実装も検討するという結論になりました
(リリース後、既存アプリケーション開発者による移行状況から、headerでの指定による手段で移行要件が満たせていることがわかり、今回はにアプリ管理画面でバージョン指定ができるようにする実装は見送りました)
2020年3月頃: 新バージョンリリースに向けたリリースプランニング
実現方法が明確になったので、2月あたりからリリースに向けたプランニングを行いました
とりあえず大まかにどれくらいかかりそうかという見積もりを行い、PMやビジネス側のメンバーとも話し合ってリリース日を2020年6月15日に決定しました
この時点で新バージョンが Version: 2020-06-15 に決定しました
現在公開されている会計freeeのPublic APIのリファレンスにも大きくバージョン名が記載されています
2020年4月頃: バージョン切り替えの仕組み、新旧両バージョンでrequest validationを行うための実装
バージョン切り替えの仕組みの実装
4月頃から実装を開始しました
まずポイントになったのが「ソースコード上どこで新旧どちらのバージョンを指定してリクエストしてきたかを判別するか」です
前提として、会計freeeはRuby on Railsを利用しています 話し合った結果、もともと会計freeeに導入されていたrequest_storeというgemを利用することにしました
https://github.com/steveklabnik/request_store
簡単に説明すると、request_storeを使うことでrequestごとにglobalな変数を設定することができます
新バージョンを利用する際には X-Api-Version: 2020-06-15 をつけてもらう仕様としたので
requestが送信されるたびに、request_storeで
headerがあれば新バージョン
headerがなければ旧バージョン
であることを表す値を version という変数に代入します
これを各controllerやserializerなどで参照することで、新旧どちらのバージョンの挙動にするかを判定できるようにしました
新旧両バージョンでrequest validationを行うための実装
会計freeeのPublic APIではcommitteeというgemを利用してrequest validationを行っています
https://github.com/interagent/committee
このgemはOpenAPI Specificationに準拠したschemaを読み込ませることで、schemaにあわせたvalidationを行ってくれるものです
committeeは以下のようなやり方でschemaをしていた上でRackに入れて使用します
use Committee::Middleware::RequestValidation, schema_path: 'docs/schema.json', coerce_date_times: true
ただ複数のスキーマを読み込めるような仕様にはなっていないのでそのままでは新旧両バージョンでvalidationを行うことができませんでした
チーム内で相談した結果、RequestValidation層を2層挟み、またスキーマバージョンを判定する変数を渡せるようにしました
それぞれの層で上で書いたversionという変数を参照し、自らの対象バージョンでなければそのまま下位のRack層にrequestを流す方法を取りました
use Committee::Middleware::RequestValidation, schema_path: 'docs/old_schema.json', pubilc_api_version: 'v1' use Committee::Middleware::RequestValidation, schema_path: 'docs/old_schema.json', pubilc_api_version: 'v2020-06-15'
このようにしてどちらのバージョンでもrequest validationを行えるようにしました
2020年5, 6月頃: 実際の破壊的変更対応を実装
バージョンを判定することができるようになったので、あとは 1月に決めたスコープの破壊的変更をガシガシと実装していきました
ただものによっては仕様が複雑でスッと実現できないものも多くありました
APIチームはエンジニア5人ほどで会計、人事労務の全エンドポイントを担当しているので全てのAPIを完全に把握するのは正直不可能です
そもそもこれは会計ソフトとして正しいんだっけ?みたいな部分はドメインロジックを担当しているチームに相談しながら実装を進めていきました
2020年6月15日: 新バージョンリリース!!!!!
バージョン名に日付が入っているので遅れると相当カッコわるいぞというプレッシャーがありましたがなんとか間に合わせることができました
並行運用期間
6月から12月までの並行運用期間は大きな出来事はありませんでしたが以下のことには気をつけていました
- 細かな修正があったときに忘れずにAPIリファレンス含め、新旧両バージョン変更を加えること
あたりまえなんですが地味に見落としが多く、僕自身もプルリクエストのレビュー時に何度も指摘し、された覚えがあります
たった2つのバージョンを並行運用していても意識から漏れてしまうことがあったので、複数バージョンサポートとなった場合なにかしらの仕組みで自動で反映されないと厳しそうだなと感じました
また、この期間エンジニアはあまりやることはなかったですがビジネス側のメンバーはいろいろと働きかけてくれていました
- 移行対象のエンドポイントのrequest数を確認して移行状況のチェック
- freeeのdeveloper community参加者への全体連絡
- freeeアプリストアに公開されているアプリのうち、移行対象のエンドポイントをつかっているアプリのオーナーに対して別で連絡
12月に無事旧バージョンのサポート廃止ができたのは間違いなくビジネス側のメンバーのおかげです
APIの新バージョンはエンジニア、ビジネス全員が一丸となって取り組まないと実現できないんだなと身をもって感じました
以上のことを乗り越えて、会計freeeのPubilc APIは現在新バージョンで稼働しています
興味のあるかたはぜひdeveloper siteから確認してAPI callしてみてください