@kyanny's blog

My thoughts, my life. Views/opinions are my own.

例えば OSFA な API をやめる

OSFA == one-size-fits-all 単一の API で全てをカバーするのをやめたらどうか、ということ。

APIのバージョニングは限局分岐でやるのが良い - Hidden in Plain Sight

この話のポイントとはちょっとずれてる && Podcast 聴いてないのですが。

Quipper プラットフォームで内部的に利用されている API も、 /v1 というパスの下にはえててごく一部のエンドポイントだけ /v2 がある、みたいなよくある状態だったんだけど、ここ二三ヶ月くらいで

  • クライアントアプリケーションに特化した一連の API を必要に応じて実装する
  • それらの API は「UI べったり」に最適化されている

という風に(たぶん @miyagawa さんが言っているのと似た形に)変わってきた。どういうことかというと。

Quipper は GAKUMO とか Quipper School とか昨日リリースしたばかりの Quipper Training とか、複数のサービスを展開している。ソースコードレベルで同じアプリケーションをブランドだけかえて使い回していることもあれば、完全に独自のアプリケーションを実装して使っていることもある。それらのアプリケーションはすべて単一の API を参照しているんだけど、一種類の、一般的な API だけではどうしてもクライアント側の実装に無理がでてしまう。

そこで、もう割り切ってクライアントごとにネームスペースを切り、それぞれ独自の API を実装したほうが素直な実装になるし、コンフリクトも無くなるし、開発スピードも上がるのではないか?と思って試してみたところ、うまくいった。具体的には、

  • Boofy というアプリケーションは /boofy/v1 以下に
  • Soozy というアプリケーションは /soozy/v1 以下に

必要な API を実装する。開発拠点がロンドンと東京に分散しており、それぞれ別サービスを開発しているが API は共有している、という状況下においてこの隔離戦略はうまくフィットした。

こうして Backbone.js や Chaplin.js をベースに実装されたクライアント向けに最適化された API を提供できるようになり、クライアント側の実装をシンプルに保てたり、 HTTP リクエストの回数を減らしてパフォーマンスを向上させることができた。例えば、

  • ダッシュボード画面を描画するために必要な全てのデータを返す /boofy/v1/dashboard API とか
  • ユーザーが学習できるコンテンツを全てまとめて返す /soozy/v1/contents API とか

そういう「UI べったり」な API を実装している。なお /v1 がついてるのは過去の慣習に引きずられている側面が大きく、バージョニングよりも上位の部分で分離してしまおうという考え方なので、実は /v1 は無くてもいいと思っている。

API と聞いて多くの人がイメージする RESTful でジェネリックな API とは真逆で、パッと見だといかにもまずそうなパターンに見えるが、 Quipper ではどのエンジニアも JavaScript/CoffeeScript でクライアントを書きつつ Ruby で API サーバも書くので、互換性やら調整やらでブレーキがかからなくて済むぶんだけ開発サイクルははやくなった。コードレビューもサービスごとの担当チーム内でうまく回せている。

このアーキテクチャ、実は Netflix の API デザインを参考にしている。

The Netflix Tech Blog: Embracing the Differences : Inside the Netflix API Redesign

タブレット、スマートフォン、 AppleTV のようなセットトップボックス、さらには PlayStation3 のような据え置きゲーム機など、さまざまなクライアントからアクセスされる Netflix の API は、各クライアントごとに特化したインタフェースを提供するアダプタ層と、その裏側にあるジェネリックな API との二層構造になっている。

Quipper API は、このデザインのうちクライアントに特化したアダプタ層を作るという点に注目して、そこを抜き出して適用した状態といえる。MongoDB の O/R マッパを利用したモデル層が独立したライブラリになっていて API サーバのインスタンス内にロードされているので、このモデル層を MongoDB に対する API とみなせば二層構造になっていると言えなくもない。

このやり方が正解だ、と言うつもりはない。というか、 API デザインに正解なんて無いのだと思う。どんなに美しくデザインされた API であっても、クライアントから利用しづらければ有用だとは言えない。大事なことは常に進化していくことで、変更に耐えうるデザインをいかに維持するのか、そのためのアプローチは複数あっていい。バージョニングを工夫するのも一案だと思う。

個人的には、そもそも単一のシリーズの API だけで様々なクライアントからの要求を満たせると考えること自体が間違いだったんじゃないか、と思っている(これも先の Netflix のブログ冒頭で "Netflix has found substantial limitations in the traditional one-size-fits-all (OSFA) REST API approach." と言及されているが)