mcpField
B
bff-design

bff-design

github.com/citypaul開発76060 回インストール4 週間前に更新
リポジトリ

bff-design について

BFF の採用要否や分割・運用方針を決めるフロントエンドチーム向けのアーキテクチャスキルです。体験ごとの所有権、責務の境界、上流集約の耐障害性、ユーザー ID の伝達を扱い、共有公開 API や業務判断層への変質を避けます。

主な機能

  1. 01BFF の採用時期と数の検討
  2. 02プロダクト体験ごとの所有権設定
  3. 03表示整形と業務判断の分離
  4. 04並列集約と部分障害への対応設計
  5. 05上流へのユーザー ID 伝達指針

活用シーン

  1. 01単一ページアプリに BFF が必要か判断
  2. 02複数のクライアント体験の BFF 境界を設定
  3. 03上流障害時の縮退レスポンスを設計
  4. 04共有 BFF の公開 API 化を回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/citypaul/.dotfiles --skill bff-design

インストール後、Claude Code で/bff-designを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
references/
adoption-and-granularity.md
aggregation-and-resilience.md
alternatives.md
upstream-identity.md

詳細

BFF の設計

バックエンド・フォー・フロントエンドは、技術である以前に所有権のパターンです。クライアントアプリケーションが使用する API は そのアプリケーションの一部であり、フロントエンドを所有するチームが所有し、ほかのアプリケーションには利用されません(このパターンの考案者である Calçado による定義)。アーキテクチャ上は、単一のユーザーエクスペリエンスを担うホストです。データを整形、集約、仲介しますが、意思決定は行いません。

このスキルでは、パターンレベルの判断を扱います。BFF を導入するか、いくつ導入するか、それぞれが何を所有できるか、上流サービスの障害にどう対応しながら集約するか、ユーザーのアイデンティティを上流サービスへどう引き継ぐかを決めます。BFF 自体の境界を保護する場合は bff-entry-points を読み込みます。アクセスの分類、準備済みのレジストラー、セッション/CSRF、リアルタイム通信の保護、適用ゲートを扱います。このスキルでは、これらを繰り返しません。物理的な endpoints/ ツリーについては structure-codebase を、駆動ホストの位置づけについては hexagonal-architecture を、管理していない利用者との契約については api-design を(健全な BFF にそのような利用者はいません)、上流アイデンティティのリファレンスが扱う OAuth/OIDC プロトコルの不変条件については secure-oauth-oidc を読み込みます。

判断を下す前に、該当するリファレンスを読みます。

  • BFF を導入するか、いくつ運用するか、誰が所有するか、モバイルのバージョン差、デプロイ単位とモジュール単位のどちらにするかを判断する場合は、references/adoption-and-granularity.md を読みます。
  • BFF から上流サービスへファンアウトする場合は、references/aggregation-and-resilience.md を読みます。構成、部分的な障害、タイムアウト/再試行の予算、キャッシュを扱います。
  • BFF がユーザーに代わって上流サービスを呼び出す場合は、references/upstream-identity.md を読みます。トークン交換、スコープの縮小、取り違えた代理人の防止、失効の伝播を扱います。
  • 共有ゲートウェイ、GraphQL フェデレーション、トークンを仲介するバックエンド、SPA から API への直接接続、メタフレームワークのサーバーと BFF を比較検討する場合は、references/alternatives.md を読みます。

BFF は必要ですか?

シグナル説明
2 つ目のクライアント タイプ(モバイルアプリ、パートナー向け画面、TV)が加わる導入を促す最も強い要因です。Newman は、最初から利用者ごとに BFF を検討することを推奨しています
1 つのページ/画面から多くの下流呼び出しが発生する集約はサービスに近いサーバー側で、エクスペリエンスごとに整形して行います
フロントエンドチームがエンドポイントを追加するたびに汎用 API チームの対応待ちになるSoundCloud が直面した問題です。BFF によって契約をフロントエンドチーム内に移せます
ブラウザーアプリが機密データや個人データを扱う(デフォルトでは、ログイン機能を持つアプリはすべて該当します)IETF のブラウザーアプリ向け BCP トラックのガイダンスは、セキュリティ上の理由だけでも BFF アーキテクチャを強く推奨しています。トークンをサーバー側に保持できるためです
インターフェースが 1 つだけで、単純な類似リクエストを 1 つのバックエンドに送るAzure が明示する「適さない」ケースなので、導入は避けます。ただし、ほかの行に該当する場合は除きます。各行が答える問いは異なり、セキュリティの行だけでも、機密データを扱う単一バックエンドの SPA には BFF を導入する理由になります(トークンを閉じ込める目的に、集約の動機は必要ありません)
フロントエンド固有のリゾルバーを備えた GraphQL や、メタフレームワークのサーバー(Next/Remix/SvelteKit)をすでに運用しているすでに BFF を 持っている可能性があります。別の構成要素を追加するのではなく、その BFF にこのスキルを適用します

メタフレームワークのサーバーは BFF です。Remix は自らのサーバーをまさにこのようなものとして説明しており、Next.js のルートハンドラーも同じ責任を担います(リスクも同じです。デフォルトで一般公開されているため、bff-entry-points がそのまま適用されます)。

1つの体験、1つの BFF

粒度の単位は 体験(experience) です — どのプラットフォームが描画するかに関わらず、1つの一貫したプロダクト面を指します。同じ体験を描画する iOS と Android のリスナーアプリは、2つのビルドを持つ1つの消費者であり、2つの消費者ではありません。クリエイターアプリは別の体験であり、独自の BFF を持ちます(SoundCloud の実践、Newman のルール)。

  • 体験がデフォルトの数を決めます。チーム構造はそれを 上書き できます。iOS と Android のチームが分かれている場合、ほぼ同一の体験であっても別々の BFF を正当化できることがあります。このパターンの価値は、チームが自分たちのペースを自分で握ることにあるからです(Conway)。1つの体験を2つのチームが提供するのはオーナーシップの臭いです — 単一のオーナーチームを選ぶか、分割を受け入れるかのどちらかであり、BFF をチーム間で共有してはいけません。
  • フロントエンドチームが自分の BFF を所有します。言語、リリースケイデンス、優先順位も同様です。バックエンドのプラットフォームチームが所有する BFF は、このパターンが排除するために存在する調整の待ち行列を再現してしまいます。
  • BFF とフロントエンドは1つの論理的なデプロイ単位です。実用的な範囲で同じリポジトリに置き、コントラクトはバージョン管理するのではなく共進化させます。バージョニングの規律は 消費者の想起可能性 に応じてスケールします — その採用の参考資料には、Web の緩和とモバイルの集中という分割、および CDC という代替手段が含まれています。

形を整える。決定はしない。

BFF はプレゼンテーションモデルの作業を所有します。集約、再形成、ペイロードの間引き、体験ごとのデフォルト、そして上流の結果を1つの UI が描画できる形に変換することです。以下は所有しません。

  • ビジネスルール。 価格設定、資格、ワークフローの状態 — BFF がこれらを決定した瞬間に、それはドメインサービスと競合し、2人の主人のために変更することになります。SoundCloud が文書化した失敗モードは、機能統合とそのビジネスロジックが BFF 内に堆積していくことです。境界線上のテストは次のとおりです。リストをこのユーザーのティアが 見ることのできる ものに絞り込むのは決定です(認可の形をしており、ドメイン操作に属します)。この画面のために返ってきたものを切り詰めたり、並べ替えたり、間引いたりするのは形を整えることです。判断に迷ったら、以下の削除テストを適用してください。
  • プロダクトの認可。 呼び出すアプリケーション操作の中で決定されます(bff-entry-points、ヘキサゴナルの参考資料)。BFF は正直なプリンシパルを提供するだけで、それ以上のことはしません。
  • 永続的なドメイン状態。 BFF はキャッシュしたり、セッション単位・接続単位の状態を保持したりしてもかまいません。記録のシステムはドメインサービスの背後に存在します。

決定の形をしたロジックは、重複があろうとなかろうと、直ちに ドメインサービスへと下げられます — はしごが統べるのはプレゼンテーションに隣接する重複だけです。そのようなロジックが2つ目の BFF に現れたときは、BFF を上位に統合することに抵抗してください — 「複数の関心事が押し込まれた、ひどく肥大化したコード」(Newman)。救済のはしごは次のとおりです。まず重複を許容します。共有ライブラリを抽出するのは非ドメインの配管だけにし(そして BFF をまたぐアップグレードの連鎖に注意してください)。3回目の発生時 — 3のルール — に、その繰り返されたロジックがそもそもドメインの形をしていたのではないかと問い、そうであればすべての BFF の下にあるドメインサービスへと 下げて 押し込みます。3のルールがスケジュールするのは プレゼンテーションの配管の抽象化 です。ビジネスや認可の決定に3回の無料パスを与えることは決してありません。

集約、レジリエンス、ID — 簡潔版

  • 並列にファンアウトし、リクエストごとの期限を伝播させます。その期限は、すでに経過した時間を差し引いたものにします。各アップストリームには、推測ではなくレイテンシのパーセンタイルに基づく実測タイムアウトを設定します。
  • 各エッジでリトライを担当するのは必ず1か所だけにし、予算を設けてジッターを加えます。リトライが階層化されると、自ら引き起こすサービス拒否につながります。
  • レスポンスの各セクションを、契約上、必須か任意かに分類します。ページ全体を失敗させるのではなく、任意のセクションは劣化させます(古いキャッシュ → デフォルト値 → エラー付きの空値)。部分レスポンスの形式は、明示的で予測可能なものにします。
  • キャッシュするなら、キーにIDを含めます。そうでなければキャッシュしません。リクエスト内のメモ化は常に安全です。共有キャッシュでは、テナント/ユーザー(または、実際に使用する、より粗い属性)をキーにします。IDによって変わるレスポンスをキーなしでキャッシュすると、キャッシュポイズニングによる漏えいが起きます。
  • アップストリームには、BFF のサービス ID とユーザーヘッダーを組み合わせるのではなく、ユーザーの ID を転送します。前者は混乱した代理人の問題を引き起こします。アクセストークンを使う場合は、セッションに紐づいたトークンをアップストリームごとに交換し、対象範囲を限定してスコープを縮小したトークンを取得します(交換できない場合は、リファレンスの段階的な方法に従い、リソースごとにトークンを取得します)。トランザクショントークンモデルを採用するデプロイメントでは、代わりに、信頼ドメインにスコープされた署名済みコンテキストを、そのモデル独自のルールに従って変更せずに伝播させます。いずれの場合も、アップストリームは検証済みのクレームに基づいて認可します。

これらの各項目は、出典と仕組みを含むリファレンスのセクションを要約したものです。実装する前にリファレンスを読んでください。

パブリック API の障害パターン

2つ目のエクスペリエンスが BFF を利用した時点で、それはもはや単一のエクスペリエンス向けではありません。契約が固定され、調整のコストが再び生じ、「アプリケーションの一部」がいつの間にかバージョン管理されていないパブリック API になります。次の3つのガードレールを設けます。

  1. サーフェスを分離し、それを厳密に運用します。 ブラウザーアプリとネイティブアプリはパブリッククライアントです。これらが持つ情報によってクライアントソフトウェアを暗号学的に認証することはできません。ネイティブアプリの登録で識別できるのも、アプリのバイナリではなく登録そのものです(RFC 8252)。したがって、「クライアントを認証する」ことは文字どおりにはできません。実際にできるのは、各 BFF を独自の認証情報モードを持つ独自のホスト/オリジンにすることです。Web BFF のブラウザーリクエストポリシーとセッション発行(bff-entry-points)により、ブラウザー起点のサーフェス間の不正利用を排除します。モバイル BFF は、発行者、対象者、認可パーティーのクレームがそのサーフェスを示すユーザーベアラートークンのみを受け入れます。独自のセッションを取得した、意図的なスクリプトクライアントは、これらのポリシーと不正利用対策の対象となるのであって、暗号学的に排除されるわけではありません。過大な主張をするのではなく、脅威モデルにその旨を記載してください。アテステーション API とクライアントテレメトリは、追加のリスクシグナルとして扱い、検証済みの ID として扱ってはいけません。
  2. 新しいコンシューマーには、独自のサーフェスを用意します。 別のエクスペリエンス(自社で構築・運用するパートナー統合も含みます。SoundCloud は専用のパートナー BFF を運用しています)には、独自の BFF を用意します。自社で制御しないコンシューマーや、フロントエンド以外のコンシューマーには、正式なパブリック API を用意します(api-design)。データフィードはエクスペリエンスでも、BFF が提供すべきものでもありません。BFF にはフィード元となる永続状態がないため、ドメインサービスから提供します。
  3. 監視します:リクエストごとに、受け入れた認証情報モード、発行者、クライアント登録を記録します(ワイドイベントの形式については observability を参照してください)。BFF のテレメトリで見慣れない登録や認証情報モードが見つかった場合、それはサポート上の問い合わせではなく、アーキテクチャ上のインシデントです。

乖離からの復旧(監査で、すでに2つ目のコンシューマーが結合していることが判明した場合):その事実を明示し、契約への変更は追加的なものに限定して凍結し、コンシューマー独自のサーフェス(BFF またはパブリック API)を立ち上げます。非推奨日を公開したうえで、ルートごとにストラングラー方式で移行し、最後に境界での拒否を適用します。先に適用すると、受け入れていたコンシューマーが壊れます。適用しなければ、乖離がそのままアーキテクチャになります。

アンチパターン

  • 2つ目のエクスペリエンス、別のチーム、または制御できない任意の利用者が利用する BFF。(1つのエクスペリエンスを2つのプラットフォームでビルドしたもの――同じアプリの iOS と Android のレンダリング――は、利用者が2つではなく1つです。)
  • ビジネスルール、プロダクトの認可、または信頼できる唯一の情報源となるシステムが BFF に置かれている。
  • すべてのクライアントに対応する汎用「BFF」1つ――それは流行りの名前を付けた、画一的な API です。
  • 重複をなくすために BFF を上位で統合する。共有ドメインロジックは下位に移すべきです。
  • フロントエンドチームが利用するものを、プラットフォーム/バックエンドチームが所有する。
  • 順次的なファンアウト、すべてのレイヤーでの再試行、任意のセクション1つのアップストリームが停止しただけでページが 500 エラーになること。
  • BFF がクライアントクレデンシャルと X-User-Id ヘッダーを使ってアップストリームを呼び出す。
  • すでに BFF として機能するメタフレームワークサーバーの横に、別の BFF サービスを追加する。
  • モジュラーモノリス型 BFF(エクスペリエンスごとのモジュール、デプロイ可能な成果物は1つ)で同じ境界を運用コストのごく一部で実現できるのに、N 個のマイクロサービス型 BFF を採用する。各 BFF は、独自のライフサイクル、セキュリティ境界、SLO を持つ完全なデプロイ可能な成果物です。

完了チェック

  • この BFF が提供する単一のエクスペリエンスと、それを所有する単一のチームを明確に挙げられますか?
  • 2つ目のクライアント種別には独自の BFF を用意しますか。また、境界では現在、サポート対象外のオリジン、クレデンシャルモード、不明なクライアント登録を拒否し、スクリプトクライアントに残るリスクを脅威モデルに記録していますか?
  • BFF 内のロジックはすべてプレゼンテーションに特化していますか。ビジネスルールを失わずに BFF を削除できますか?
  • 集約コントラクトでは、劣化を許容するセクションを明示していますか。また、任意のアップストリームが1つ失敗してもページは表示を保てますか?
  • エッジごとに再試行を担当する箇所は厳密に1つで、期限は伝播されていますか?
  • アップストリーム呼び出しには BFF 自身のものではなく、ユーザーの検証済み ID が含まれていますか――交換され、対象者が限定されたアクセストークン、またはそのモデルのルールに従った、変更されていない信頼ドメインのトランザクショントークンですか?
  • BFF 自身のエントリーポイントは、bff-entry-points に従って分類され、適用されていますか?
  • メタフレームワークサーバーを実行している場合、このスキルと bff-entry-points を適用していますか。それとも「単なるフロントエンド」として扱っていますか?

コメント