Open46

Backends for Frontends (BFF) 再考

Shinya FujinoShinya Fujino

業務で BFF の開発をおこなっているが、その役割を十二分に活かすことができていないという感覚がある。明確に言語化できる問題点もあれば、ふわっとした違和感程度のものもあり、いずれにせよ、自分の BFF に対する理解が不十分な点を改めなければならないという結論に帰着した。そこで、BFF に関するインターネット上の信頼できそうな情報を読んでいき、ここにまとめていこうと思う。

Shinya FujinoShinya Fujino
Shinya FujinoShinya Fujino

OSFA API (one-size-fits-all、すなわち、汎用的な万能 API) により 800 以上のデバイスすべてに対応することの限界から出発し、従来の API を platform for API development、つまり API 開発のためのプラットフォームへと進化させるという話。汎用的な API ではなく 個別最適化された API を選択するということ。

Shinya FujinoShinya Fujino

ベースとなる考えは以下に要約されている:

  • Embrace the Differences of the Devices
  • Separate Content Gathering from Content Formatting/Delivery
  • Redefine the Border Between “Client” and “Server”
  • Distribute Innovation
Shinya FujinoShinya Fujino

Embrace the Differences of the Devices

OSFA API は、API を提供する側にとっては都合がいいが、API を消費する側にとってベストであるとは限らない。

  • メモリや CPU などハードウェアレベルで決まる処理速度
  • 最適なドキュメント構造
  • 表示する要素を決めるスクリーンサイズ
  • ストリーミングのほうが処理速度が早くなる場合など、最適なコンテントデリバリの方法
  • ユーザーの操作方法

などが各デバイスごとに異なることため、OSFA ではない、デバイスごとに最適化された API を提供したいという問題意識があった。こうしてデバイスごとに異なるエンドポイントが必要となり、新たなアーキテクチャを考える必要が生まれた。

Shinya FujinoShinya Fujino

なお、記事からリンクされている
https://www.programmableweb.com/news/why-rest-keeps-me-night/2012/05/15
では、この点がよりフォーカスされ具体的に論じられている。要は、OSFA API は汎用的であるため厳格なルールがあり、その結果、増え続けるデバイスに対応してスケールできず、また各デバイスに対する最適化もできない、ということ。これが 10 年前の Netflix の問題意識だった。

Shinya FujinoShinya Fujino

Separate Content Gathering from Content Formatting/Delivery

データを取得し、ペイロードを作成し、最終的にそれを送信するという役割を担っていた従来の API を、

  • 取得 (Content Gathering)
  • 加工、送信 (Content Formatting/Delivery)

という二つのレイヤーに分割したという話。以下の図

は、JAVA API が前者の責務を、その上にある各デバイスが描かれたレイヤーが後者の責務を担うことを説明している。また、デバイスごとに異なるエンドポイントが存在することも描かれている。

Shinya FujinoShinya Fujino

Redefine the Border Between “Client” and “Server”

従来は、デバイス上で動くコードが「クライアントコード」であり、ネットワークを境界としてサーバ上で動くコードが「サーバコード」であった。このクライアントとサーバの境界をサーバ側へとずらし、特定の UI に向けた処理をサーバ側にもつことを考える。このレイヤーを Netflix は adapter と呼んでおり、

  • データ取得を依頼するリクエスト
  • 不要なデータの刈り取り
  • エラーハンドリングやリトライ
  • データの整形
  • データのクライアントへの送信

などをおこなう。これは現代的には BFF に対応するだろう。

ネットワーク越しのリクエストの減少、そして次に述べる、最適化された adapter を作成するための責務の移譲というメリットが生まれる。

Shinya FujinoShinya Fujino

Distribute Innovation

デバイスごとに adapter を作成するのは、逆に大変ではないだろうか?開発の速度を落とさないようにするにはどうしたらいいだろうか?

ここで、上で述べた「従来の API を API 開発のためのプラットフォームへと進化させる」という話につながる。つまり、サーバ側で動く adapter は (バックエンドチームではなく) 各 UI チームが実装し (distribute the API development to the UI teams)、従来の API は adapter へと従事することに務めるのである。これにより、クライアントは汎用的な API の制約に縛られず、クライアントに最適化された API を消費することができ、またサーバサイドのルールから独立した自由な開発が可能となる。これによってイノベーションの速度を落とすことなく成長することができる。

Shinya FujinoShinya Fujino

以上が大まかな内容となる。当時の具体的な問題意識から出発し adapter というレイヤーに至る経緯が大変腑に落ちる、いい記事だった。adapter の役割も明確に書かれており、考えを整理することができた。

余談だが、少し前に読んだ Jeff Meyerson の Move Fast: How Facebook Builds Software にも、同じ頃に Facebook が対応デバイスの種類の増加に苦しみ失敗しながらも、GraphQL などの新しい技術を生み出していく過程が描かれていた。今もその難しさはそれほど変わっていない気がするが、BFF も含めた現代的なアーキテクチャや技術の根源へと至るためには、その頃、つまりスマホが爆発的に普及した直後の状況を見ていく必要があるのだろうと思う。

Shinya FujinoShinya Fujino
Shinya FujinoShinya Fujino

Motivation

肥大化したモノリシック API がモバイル端末などに対する最適化を意識しておらず、そうした対応をバックエンドチームへと依頼するコミュニケーションコストも大きく、またバックエンド側も個別最適化と汎用性のバランスを保ちながら開発することが難しくなっていた、という話。大筋では Netflix が抱えていた問題と同じようなものだと考えていいだろう。

Shinya FujinoShinya Fujino

Frontend Developers Writing Backend Code

iOS や Android アプリ開発者がバックエンド開発へと進出することとなったが、やはりそれは簡単ではない。そこで、バックエンドチームは

  • alerting
  • モニタリング
  • テレメトリ
  • 認証
  • rate limiting
  • リクエストのクリーニング

などをおこなうライブラリを作成して手助けした。これにより BFF の作り方が標準化され、各チームがフルスクラッチで BFF を作る手間が省かれた。

Shinya FujinoShinya Fujino

BFF as a Migration Path

BFF により、バックエンド API を直接呼び出すことがなくなった。その結果、BFF の背後でモノリスをマイクロサービスへと移行するプロセスがシンプルなものとなった。Strangler pattern との類似性も語られており、BFF は旧パブリック API の Strangler としての役割を担った。

Shinya FujinoShinya Fujino

One or Multiple BFFs

BFF をどれだけ作成すべきか。プラットフォームごと、機能ごと、ユーザー属性ごと、などの基準が考えられる。

SoundCloud では、アップストリームのユースケースごとに分けるという手法を取っている。アプリの特定用途向け、埋め込みウィジェット向け、他サービス向け、など。

Shinya FujinoShinya Fujino

共通ライブラリの難しさも語られている。

  1. ライブラリに未成熟な機能を実装してしまうことでバグや不安定なインターフェイスを BFF 全体に拡散させてしまう
  2. 柔軟性と拡張性を許容することが、BFF 間の実装のバラツキにつながる
  3. ライブラリを小さく compostable に保つこと
Shinya FujinoShinya Fujino

Evolution of a BFF

BFF が増加するにつれ、似たような機能や実装が重複していく。

ライブラリとして共通化するか、あるいはサービスとして共通化するかの基準は、「抜き出す機能が同時に更新される必要があるタイプのものであればサービス、そうでなければライブラリ」とのこと。なるほど。

初めから過度に一般化させようと考えないことが大事。まずは特定のユースケースが重要であり、抽象化はあとで考えればよい。

Shinya FujinoShinya Fujino

以上が大まかな内容。モノリスをマイグレートする手段としての BFF という発想は、言われてみれば納得するが普段あまり考えていない視点だった。

BFF の粒度については自分も悩んでいる点だ。どのような粒度にせよ、フロントエンドエンジニアが不慣れな作業をクオリティを保ちつつこなしていくために、共通ライブラリ的なものを用意するというのは重要そうだ (コードのかたちであれ、ドキュメントのかたちであれ、そういったものを用意し、BFF ごとに完全に独立した設計や実装にはしない方がなんとなくよさそうではある。が、どうせ他のプラットフォームで使わないなら、スピード感や捨てやすさなどとのバランスも重要視すべきかなとも思う)。

Shinya FujinoShinya Fujino
Shinya FujinoShinya Fujino

Context and problem と Solution はこれまで読んだものの要約という感じで、特筆すべきことはない。Issues and considerations と When to use this pattern はポイントが整理されていて、設計の際のチェックリストとして使えそう。

Shinya FujinoShinya Fujino

Issues and considerations

  • Consider how many backends to deploy.
  • If different interfaces (such as mobile clients) will make the same requests, consider whether it is necessary to implement a backend for each interface, or if a single backend will suffice.
  • Code duplication across services is highly likely when implementing this pattern.
  • Frontend-focused backend services should only contain client-specific logic and behavior. General business logic and other global features should be managed elsewhere in your application.
  • Think about how this pattern might be reflected in the responsibilities of a development team.
  • Consider how long it will take to implement this pattern. Will the effort of building the new backends incur technical debt, while you continue to support the existing generic backend?
Shinya FujinoShinya Fujino

When to use this pattern

Use this pattern when:

  • A shared or general purpose backend service must be maintained with significant development overhead.
  • You want to optimize the backend for the requirements of specific client interfaces.
  • Customizations are made to a general-purpose backend to accommodate multiple interfaces.
  • An alternative language is better suited for the backend of a different user interface.

This pattern may not be suitable:

  • When interfaces make the same or similar requests to the backend.
  • When only one interface is used to interact with the backend.
Shinya FujinoShinya Fujino
Shinya FujinoShinya Fujino

Introduction

The General-Purpose API Backend

Introducing The Backend For Frontend

このあたりは既視感 (既読感?) がある。汎用的な API はデバイスごとの最適化が難しく、またそこが開発のボトルネックとなりやすい。また API チームなどが作られてコミュニケーションコストが増大するという話。そこで各クライアントに最適化された BFF が登場する。

Shinya FujinoShinya Fujino

How Many BFFs?

BFF をいくつ作成するべきか。

  • 一つのクライアント (たとえば iOS アプリ) につき一つの BFF
  • 同じタイプの UI (たとえば iOS アプリと Android アプリ) につき一つの BFF

という二つのモデルが考えられるが、基本的には前者が好ましい。後者は複数のクライアントの要求を考慮することにより肥大化する可能性が懸念としてあるため。ただし、たとえば iOS アプリと Android アプリと BFF を同じチームが担当している場合はこれを許容してもよいかもしれない。こうした組織の構造がどちらのモデルを選択すべきかを決める要因となりがちである。

一つの体験につき一つの BFF (one experience, one BFF) という考え方もある (Stewart Gleadow の提案)。

Pete Hodgson は、チーム構造により BFF の数を決定すべき (一つの UI チームにつき一つの BFF) と主張した。しかし、一般に、システムの構造に比べチームの構造はより流動的であり、チームの分割 (たとえばモバイルチームを iOS チームと Android チームに分割する) に比べ BFF を分割することは容易ではない。このとき、あらかじめ BFF を分けておけば、チームの分割にも対応しやすくなる。

Shinya FujinoShinya Fujino

And Multiple Downstream Services (Microservices!)

BFF がマイクロサービスを集約 (aggregate) するためのパターンとして有用であるという話。このあたりは当然という感じがする。

BFF から発せられる複数のリクエストのうちの一部が失敗した場合における対処もまた BFF の責務のうちの一つ。一部のリクエストが失敗することを許容し、不完全なデータを返すことも考慮する必要がある場合がある。その場合はもちろん、クライアントは不完全なデータをレンダリングできなければならない。

Shinya FujinoShinya Fujino

Reuse and BFFs

BFF 間のコードの重複は基本的に許容できるという話。重複したパターンをまとめる別のサービスなどを作れば、それが複数の事情を考慮した結果として肥大化する可能性があり、またサービス間の密結合へとつながる。これはコードの重複より憂慮すべきこと。ただ、これを認めるべき場合があるかもしれないとも。

共通処理を抽象化する一つの方法は、ライブラリにまとめること。ダウンストリームのサービスへのクライアントライブラリは密結合の原因となりやすい (なぜ?)。ライブラリのスコープが完全に一つのサービスに閉じていれば許容される。

もう一つは別のサービスにまとめること。これは対象ドメインに関する一つの新しいサービスとして概念化できる場合は有効となる。

こうした考えをさらに進め、共通処理を既にあるダウンストリームのサービスに担わせることも考えられる。

いずれにせよ、ある程度の重複は許容して構わないと主張されている。三度目の重複から抽象化を考え始めるくらいでいい (the old adage of creating an abstraction when you're about to implement something for the 3rd time still feels like a good rule of thumb, even at the service level)。

Shinya FujinoShinya Fujino

BFFs for Desktop Web and Beyond

BFF がデスクトップで動くブラウザでも有用な場合があるよという話。それはそうだと思う。

続いて、Facebook など外部のシステムと連携する BFF を作るというのは面白いと思った。この手の外部システムは API 呼び出しの方式を変えることがほぼないため、モノリシックな API で対応する場合、古い API を保守し続ける必要がある。しかし、独立した BFF を作成すれば、その他のシステムは古い API を気にする必要がなくなるというメリットがある。

Shinya FujinoShinya Fujino

And Autonomy

UI が接続する API を UI チーム自ら開発することにより、開発におけるコンフリクトも減り、開発速度が上がる。もちろん BFF がダウンストリームのサービスを呼び出す必要は残るが、少なくとも UI の開発が阻害されることはなくなる。また、サーバサイドにおける機能追加をおこなうことも、他チームとの連携なしに実現することができる。

独立性、自律性を獲得できるという点がポイント。

Shinya FujinoShinya Fujino

General Perimeter Concerns

認証やロギングなど、複数の BFF にまたがる関心事項はどうすべきか。共通化したい思いに駆られるが、レイテンシの増加、レイヤーの追加に伴うテストや開発の難しさの増加などを考えると、これらも BFF の中に閉じ込め、BFF の独立性を高める方が良いかもしれない。が、このあたりは答えを出すのが難しいようだ (I'm torn about this)。

Shinya FujinoShinya Fujino

When To Use

特定の UI や外部サービスのために機能を提供する場合は BFF を使うべき。特に、UI チームとダウンストリームサービスのチームが分かれている場合は、積極的に BFF を採用したほうがいい。

Shinya FujinoShinya Fujino

これにて読了。

BFF の数をどのように決めるべきか (おそらく UI チームの数に対応させるのが良い)、共通処理を (どう) 切り出すべきか、外部サービス対応との親和性、自律性の獲得による開発効率の向上、など、BFF に関する論点がよくまとまっている文章だった。

Shinya FujinoShinya Fujino
Shinya FujinoShinya Fujino

うーん、ZOZO Aggregation API という名前が示すように、これは BFF というよりは、バックエンドのマイクロサービス化という事情から生まれた API 集約サービスという感じがする。

Shinya FujinoShinya Fujino

UI ごとに BFF を分けたりしていないようだし、単数形の Backend for Frontends という趣き。結局ここがモノリシックな汎用 API になっており、名前だけは同じだがモチベーションも形式も一般的なBFF とは別物という感じだ。

Shinya FujinoShinya Fujino

これを見るに、BFF 用のチームが独立して存在しているようだ。UI チームが担当することで自律性や機動力を得られるという BFF の利点が消えてしまわないだろうか。

Shinya FujinoShinya Fujino

一応この発表者の方は BFF の数に関しては反省があるようだ (なぜそう考えているのかはよくわからなかったが...)

Shinya FujinoShinya Fujino

色々とアンチパターンを踏んでいるように見えるが、これで問題が生じていないのであれば、別に構わないとも言える。動画では BFF にアクセスが集中してしまったことに触れていたが、開発速度や最適化の観点でどうなのかは気になるところ。