1
/
5

Protocol Buffers によるプロダクト開発のススメ - API 開発の今昔 -

こんにちは、Wantedly People アプリの開発をしている竹野(Altech)です。今回は、Protocol Buffers についての記事になります。

Wantedly People では、2018年に Protocol Buffers (以下、Protobuf と呼ぶ)がとあるマイクロサービスに入って以降、何度か大規模に Protobuf を使った開発をしてきました。またその経験を通じて、Protobuf には単に「型がついて嬉しい」というだけではないパラダイム的な変化があることが分かってきました。

その知見を全社に展開するため、去年「Protobuf によるプロダクト開発速習会」という会を行いました。この記事の内容は、そこで話したことの前半「Protobuf を使うと開発がどう変わるのか?」になります。

なお、Protobuf にはバイナリフォーマットとしての役割とインターフェイス定義言語としての役割がありますが、ここでは後者にフォーカスして取り上げています。したがって gRPC gateway などを通して実際には HTTP を話す場合にもそのまま適用できる内容となっています。

Protobuf で変わること

まず結論から話すと、Protobuf を使うことで大きく二つのことが実現可能になると考えています。ひとつは、設計への適切な投資による 開発生産性の維持 、もうひとつは、その結果としてのモジュール化による 開発者のスケーラビリティの向上 です。

これら二つによって、 新しいものを作るスピードと、作ったものを変えていくスピードを両立できる というのがプロダクト開発におけるとても大きな価値だと感じています。つまり、新しいものを作るときは開発者のスケーラビリティによる恩恵を得て、作ったものを変えていく際はドメインの設計による恩恵を受けることができます。どういうことか、いくつかの観点で見ていきましょう。

質とスピードの両立

ソフトウェア開発における大きなパラメータとして、内部品質(​​可読性、変更可能性など)と開発期間があります。

もし、内部品質を非常に低く設定した場合は、開発期間を大きく減らせます(”うまく”やった場合の話です)。これはこれで有用で、例えばプロトタイプの構築に使えます。ただし、その代わり、継続的な改善はできません。プロダクト開発において「改善が99%」だという考え方が Wantedly にはありますが、それでいうと改善ができないのは致命的です。逆に、実装の品質を十分大きく取った場合、内部品質を度外視した場合に比べれば開発期間は長くなります。ただその分、継続的に改善することができます。

無論、内部品質とスピードは本来はトレードオフの関係ではなく正の相関があるというのが色々なソフトウェア・エンジニアリングの古典が教えてくれるところですが(t_wada さんの講演 が特にわかりやすいです)、とはいえ設計に対して “どのくらい早くお釣りがくるのか?” というのは実際問題としてプロダクトを開発する上では重要になります。

そして、Protobuf は、それと導入する前と比較して、内部品質と開発期間をかなり高いレベルで両立できるようになる道具だと思っています。感覚的には、同じ内部品質にするとして、3割くらい開発期間が変わってくるのではないかと思います。

新規メンバーの早期活躍

最近 Wantedly People のバックエンドの開発に新卒のエンジニアが二人ジョインしたのですが、「Protobuf がないときにどうやって開発していたのかわからない」というようなことを言っていました。

各 API がどういう責務を持っているのか、そもそもどのようなデータ構造がこのプロダクト・ドメインには存在するのか、というようなことは Protobuf から様々な解像度で読み取れます。この声は、それがない状態でどのようにそれを理解していたのかが分からない、というような話だと思います。

このことからもわかるように、Protobuf でインターフェイスをきちんと記述しておけば、新しく開発チームに入ってきたエンジニアが活躍できるようになるまでのリードタイムを大きく減らすことができます。今では新しく入ってきたバックエンド・エンジニアに最初に渡すものの一つに Protobuf が入っている状態です。Protobuf に知識を詰め込むための実践的なテクニックについては、後半(この記事の続編)で紹介します。

注:Protobuf が効果を発揮する状況

あらゆる道具がそうであるように、Protobuf も銀の弾丸ではないので、特に有効だと考える状況について補足しておきます。

Protobuf はインターフェイス定義言語です。インターフェイス定義言語は、コミュニケーションツールとして使えます。したがって、開発チームでコミュニケーションが発生するようなプロジェクトにおいて特に大きな効果を発揮すると考えています。ちなみに一人でも長い期間開発すると過去の自分は他人だと思った方が良いので、一人で長い期間同じソフトウェアを開発するようなケースでも有用でしょう。

開発スケーラビリティの向上

開発者のスケーラビリティが得られるとは具体的にどういうことか、直近のリニューアル・プロジェクトを例に取って説明します。

このリニューアルプロジェクトではネイティブアプリのある大きめの機能をほぼゼロから作り直しました。このプロジェクトの大まかな進行と依存関係がこの図のようになります。

初めに仕様を定義しながらそれと並行して実現可能性に懸念がある部分などについて技術的な調査を並行して行なっています。これができたら Protobuf で API を設計し、バックエンドとフロントエンドで合意します。一旦 API が合意できれば、バックエンドもフロントエンドもすぐに走り出すことができるため、並行性のある開発を行うことができるようになります。これが一つ目のスケーラビリティです。

もうひとつ、これは当初はそこまで想定していなかったことなのですが、さらに解像度を上げていくと、バックエンドやフロントエンドなど各ファンクションの中でもスケーラビリティを得ることができました。これが二つ目です。

このように二つのレベルでスケーラビリティが得ることができます。ではなぜこういうことが Protobuf があると上手くできるのでしょうか?ここまで説明してきたことは Protobuf 以前と比較するとよく見えてくるので、それについて考えたいと思います。

API を使った開発の今昔

API を使った開発について、「コミュニケーション」「開発パラダイム」「ドメイン理解」の三つの観点で考えてみましょう。

コミュニケーション

まずコミュニケーションですが、Protobuf 以前は、バックエンドのエンジニアが JSON をイシューに貼り付けて「こういう形のレスポンスで行きます」というような形で進んでいました。

経験上、これだと雰囲気的に良さそうかくらいしか議論できません。例えば、あるフィールドが必須なのかオプショナルなのかというようなことは JSON には登場しませんし、もっと込み入った取りうる値の集合についても、そうです。さらに重要なこととして、それがなんであるのかということ(意味)は、そこには書かれていないのです。

その結果、開発が進む中で API 定義についての手戻りや確認が大きなコストとなって返ってきます。つまり、プロジェクト進行上、バックエンドとフロントエンドを別けたとしても、その間で多くのコミュニケーションが発生してしま、ベロシティを低下させてしまいます。

さらに、API の実装を進める中でデータ構造の細部をしれっと変更するようなこともできてしまいます。そのようなことを意図的に行う人はいないでしょうが、「どこまでが API の契約なのか?」というのは JSON を見せただけでは意外と自明ではないので、それが起こり得ます。

これに対して Protobuf 以降は、API についてかなり厳格に合意できるようになりました。

API についての確認・議論の多くは API 定義時に明示的に行えますから、確認・手戻りを大幅に減らすことができますし、実装フェーズに入った後に合意した API を変更する際も明示的な手続きが必要になります(なお API に合意したあとは、型で保証されたモックをすぐにバックエンドから提供することで、バックエンドとフロントエンドで直ちに並列開発ができるようにしています)。

開発パラダイム

API の開発パラダイムも、RESTful API のときと比較して少し変わったなと思っています。

RESTful API では、「エンドポイント」をひとつずつ実装するということが多かったように思います。各エンドポイントは、RESTful API では「リソースAとその関連リソースB-Cを取得する」というような手続きです。つまり、リソース志向とはいえ、実装粒度自体はほぼ手続きであったのがそれまでの開発のパラダイムでした。リソースをオブジェクトとして捉えると、それをモジュール化の単位としても活用したいtところですが、そのようなことは十分にできていなかった、と言えます。

この状況は Protobuf を使うと思いのほか大きく変わりました。

Protobuf というのは message という「データ」にフォーカスした言語になっています。別で service という概念で呼び出し手続きについても記述できるようになっているのですが、message と service は明確に分離されていますし、message から書き始めることもできます。したがって、ドメイン上のオブジェクトをきちんと構築することができますし、その構築したオブジェクト群に対して、serializer など API の実装の仕組みを整えることで実装レベルでもモジュール化して行くことができます。

このことによって、エンドポイントよりもさらに細かいレベルに実装が自然と分解されます。たとえば「〇〇君はいまモックになってる “LinkCollection” メッセージの実装をお願いします」のような会話がデフォルトでできるようになります。

要するに、Protobuf を使うことで、手続きをひとつひとつ実装するようなパラダイムから、オブジェクトをひとつずつ実装するようなパラダイムに変化が起きます。

ドメイン理解

最後にドメイン理解(何を作っているかについてのチームの認識)の観点での変化がありました。

これは特にタイムライン機能のような抽象度の高い機能を開発している時に問題が大きかったのですが、これまでの開発方法だと重要なドメインオブジェクトやドメインロジックについての定義が隠れてしまうことが多かったです。貼りつけられた JSON はあくまでスナップショットであって、どのようなドメイン上の振る舞いがあるかは一部の人の頭の中にある状態、と言えば良いでしょうか。

分かりやすいケースでいうと、「これが null だったら〇〇を出さない」みたいな意味の定義はすぐに暗黙的になってしまいます。この例は非常に toy example 的な話で、実際にプロダクトにつける機能はもっと複雑な意味を内包していることもありますから、そのようなものを Protobuf なしで上手く構築する方法はありませんでした

もちろん実際にはなんとか作ることになるわけですが、意味が共有されていないので、バックエンドとフロントエンドで別々の言葉を話している、というようなことが起きて開発の継続が難しくなりました。チーム内で複数の言語・ドメインモデルが存在してしまう、という完全にアンチパターン的な状況です。「Post に実装しよう」「その Post ってどの Post?」という会話がリアルにありました笑(笑えない)。

これはドメイン設計の不備ですが、設計は実装と結びついてこそ威力を発揮します。設計と実装を結びつける道具としては Protobuf は非常に強力です。まず、API はお互いにレビューしますからそこで意味のすり合わせができます。加えて、Protobuf で定義した message の名前などはそのままバックエンドとフロントエンドの実装にも使われるので、これがさらに一貫したドメイン理解をもたらしてくれます。

今昔まとめ

まとめるとこうなります。

コミュニケーション

  • 昔:JSON をイシューに貼りつけて「こんな感じのレスポンスで行きます」
  • 今:API の内容について厳格かつ継続的に合意できている

開発パラダイム

  • 昔:複数のオブジェクトを含む手続き全体を実装していくしかない
  • 今:必要なら個別のオブジェクトを別々に実装していくことができる

ドメイン理解

  • 昔:どのようなドメイン上の振る舞いがあるかは一部の人の頭の中にある状態
  • 今:ドメイン上の重要な振る舞いは全ての開発者で共有できている状態

Protobuf を導入すると API を作ったプロダクト開発は大きく変わります。ぜひ積極的に使っていきましょう。

ここまで読んでくださりありがとうございました。この記事は速習会で話したことの前編になります。

後編では、泉(izumin)がProtobuf を使っていくための実践的な開発フローやツールチェインについて色々と説明してくれます。ここに書いたようなこともより具体的にイメージができると思いますし、OSS として公開しているツールチェインは活用していただけるかもしれませんので、ご期待ください。

29 Likes
29 Likes

Weekly ranking

Show other rankings
If this story triggered your interest, go ahead and visit them to learn more