モバイルエンジニアのためのBFF入門 (1) 技術選定の軸

これに全力で応えてみる

とりあえず第一回として、iOS / Android のための BFF (Backends for Frontends) を作りたくなったときに、どういう技術で作るかを考えてみます。第二回があるかは未定。

そもそもBFFって何という方のために、手前味噌ですが自分の登壇資料をあげておきます。

言語とフレームワークの選定

まず、いくつか観点を列挙する。

静的型付け or 動的型付け

できれば静的型付けのが良いと思う。

iOS / Android が静的型付け言語を利用しているので、スイッチングコストが少ない。

あと、たぶんそんなに頑張ってテスト書かない(書くのが難しい)ので、極力型レベルでバグを検知したい。要はBackendのAPIをstubしないとテスト書けないんだけど、せっかくテストをしてもstubが間違ってると意味がないので、そこの(叩くAPIの型の)信頼性はどちらにしろ担保しなきゃいけない。一方で、叩いたAPIをマップするところの型を静的型付けで担保してしまえば、後はそんなにテスト頑張って書かなくても割とちゃんと動くことが多いと思うので、静的型のメリットが大きい。もちろんゴリゴリロジック書くようになったらテストは書くんだけど。

ラーニングコスト

普段使っている言語 (Swift, Kotlin) に近いほうが良いだろう。一方で、そこまで複雑なことをやらないので、なるべくシンプルな言語/フレームワークを選択したい気持ちはある。

並列プログラミング

並列性は必須である。基本、外部API呼び出しを複数行うので、それが直列になるとそれだけ遅くなるから。

さらに、Non-Blocking I/O であることを強く推奨する。BFFの性質上、CPUを使ってブロックするような重たい処理はほとんどない一方で、API呼び出しを待つ時間がめちゃ長いという性質があるので、Non-Blocking I/O を採用することで、サーバーのスペックがとても少なくてすむ。(c.f. C10K問題)

以上の観点をもとに、言語/フレームワークの候補を挙げる。

Kotlin + Spring WebFlux

・静的型付け
・ラーニングコスト○ (KotlinはAndroidエンジニアにはもちろん、Swiftにもそれなりに近いのでiOSエンジニアにもやさしい。ただしSpring, Spring Webfluxの導入のコストが高いので、ある程度知ってる人がいないとスタートがきつそう)
・並列性○ (Non-Blocking I/Oである。coroutineも使える。)
・すごく枯れてるわけではないという印象

TypeScript + Node.js + (express / Koa など)

・静的型付け
・ラーニングコスト○ (JavaScriptの面倒さはあるものの、言語やフレームワークがあまり重たくないのでその点は向いていると思う)
・並列性◎ (Non-Blocking I/O でありメチャクチャ安定している。async/awaitなどプログラミングのサポートもOK)

Go

※全く詳しくないので間違ったこといってたらすみません。

・静的型付け
・ラーニングコスト○
・並列性◎ (Non-Blocking I/O, Goroutine)

所感

Node.jsを書いたことがあるならそれが一番導入が手軽そう。初めはJavaScriptでも。ブラウザじゃないのでBabelとか入れなくてもそこそこ大丈夫。

APIの技術選定

RESTful, RPC, GraphQL, ... のところの選定の話ですが、どれも扱う範囲が微妙にズレていてピンポイントで比較するのが難しいので、あえて観点をダラダラ書きます。

まず BFF は基本的には、1つの提供の仕方に対して1つ作る、という思想です。つまりBFFとマイクロサービスはm:nの関係です。モバイル向けのBFFとWebサービス向けのBFFは別に作る、といった塩梅です。

この思想に乗っかるのであれば、BFFは万能なAPIを作る必要はなく、むしろ目的に特化したAPIのほうが良いと言えます。RESTfulやGraphQLのようなリソース指向ではなく、RPC的なAPIの提供の仕方が良いのではないか、と考えられます。

一方で、リソースを全く無視したAPIを作るのも、クライアントアプリから使いにくくなります。JSONをそのままViewに出すだけのようなシンプルなアプリであれば、Viewに表示するものをそのまま返すようなAPIにしても問題ないでしょう。しかし、マイクロサービスを束ねるようなアプリであれば、そこそこのサイズであり、クライアント側にMVC系のアーキテクチャが入っているでしょうから、その場合、クライアント側の Model (= Domain Object) をそのまま意識したAPIにすると良いでしょう。

もちろんBFFという言葉の定義にそこまでこだわりはないので、複数の目的に柔軟に使えるように、GraphQLで作るといった方法もありえます。また、BackendがGraphQLなのであれば、BFFはバックエンドをマージしたようなGraphQLで提供するというのも良いでしょう (Schema Stitching) 。その場合はBFFはあまりモバイルに特化したロジックを持たない層になるかもしれません。

なお、更新系 (RESTfulならPOST/PUT/DELETE/..., GraphQLならMutation) については、BFFは基本的に手を入れるべきではないと考えています。BFFを通してもいいですがほぼproxyとして流すだけにして、更新系のオーケストレーションとかはしないほうがいいです。更新した後の戻り値に手を入れるとかは良いと思いますが。

APIのバージョニング

モバイルクライアントのバージョンをheaderで送ってもらって、それをもとに、Controller層で限局的に分岐させましょう。クライアント側の都合で都度いじっていくので、クライアントのバージョンを使ってしまうのが一番簡単です。APIのurlに v1/ などを入れるのはBFFにおいては避けたほうがいいと思います。

雑な結論

とりあえず、お試しで本番導入する感じであれば、以下のような構成でミニマムスタートするのが良いのではないでしょうか。

・TypeScript + Node.js + express
・APIはJSONを返し、あまりRESTfulにこだわらず、クライアントのユースケースに応じたAPIを作る
・バージョニングはモバイルの種類(iOS/Android)とクライアントアプリのバージョンで行う
・テストは最初はあんまり真面目に書かない。代わりにバックエンドのAPIの型と一致させるのはちゃんとがんばる

また、筆者が採用したアーキテクチャは以下のようになっています。

・Kotlin + coroutine + Spring WebFlux
・Protocol Buffers over HTTP/2
・バックエンドはOpenAPIベースのドキュメントを提供しているので、そこからJavaのクラスを自動生成して使う
・テストはひととおり書く

これらを満たしたサンプルプロジェクトみたいのがあれば、とりあえずBFFを始めてみやすい状況になるのかな?需要があるのかは気になるところ。

noteの通貨流通量を増やしていきたい!!