目次
- BFFとは
- 導入を判断する基準
- 設計原則
- API設計・データ処理
- セキュリティ
- パフォーマンス
- 耐障害性
- 可観測性(Observability)
- 開発・テスト・運用
- よくあるアンチパターン
- 実装技術の例
- チェックリスト
- 参考資料
1. BFFとは
BFF(Backend for Frontend)は、特定のフロントエンド(Web、iOS、Android など)専用に用意するバックエンド層です。クライアントとマイクロサービス群などのバックエンドの間に置き、そのクライアントに最適化したAPIを提供します。
[Web SPA] ──▶ [Web BFF] ──┐
[Mobile App] ──▶ [Mobile BFF] ──┼──▶ [User Service] [Order Service] [Product Service] ...
[Partner] ──▶ [Public API] ──┘
主な役割
| 役割 | 内容 |
|---|
| アグリゲーション | 複数のバックエンドAPIを呼び出して、1つのレスポンスにまとめる |
| データ変換・整形 | 画面に必要な形へフィールドを絞り込み、名前を変え、組み替える |
| プロトコル変換 | gRPC / SOAP / 社内プロトコルなどを、REST や GraphQL にする |
| 認証・セッション管理 | トークンをサーバー側で持ち、ブラウザにはCookieだけを渡す |
| クライアント固有の最適化 | 画面単位のエンドポイント、ペイロード削減、キャッシュ |
2. 導入を判断する基準
BFFが向いているケース
- 複数のクライアント(Web / モバイル)があり、それぞれ必要なデータの形が大きく異なる
- バックエンドがマイクロサービスに分かれていて、1画面を出すのに何本もAPIを呼ぶ必要がある
- フロントエンドチームが自分たちのペースでAPIを変えたい
- ブラウザにアクセストークンを持たせたくない(セキュリティ要件)
BFFが不要、または過剰になりやすいケース
- クライアントが1種類しかなく、バックエンドもモノリスで、APIがすでに画面に合っている
- チームが小さく、新しいレイヤーを運用するコストのほうが大きい
- GraphQL Gateway や API Gateway の機能だけで要件を満たせる
原則: BFFは「レイヤーを1つ増やす」選択です。得られる効果が、運用・レイテンシ・障害点が増えるコストを上回るときにだけ導入します。
3. 設計原則
3.1 1クライアント(体験)につき1つのBFF
- 「Web用」「モバイル用」のように、ユーザー体験の単位で分けます。
- 全クライアント共通の汎用BFFを1つだけ作ると、結局「もう1つのモノリスAPI」になり、BFFの意味がなくなります。
- iOS と Android の要件がほぼ同じなら、Mobile BFF を共有して構いません。分け方は機械的に決めず、実際の差分で判断します。
3.2 BFFはフロントエンドチームが所有する
- BFFを使うクライアントのチームが開発・運用するのが基本です(“You build it, you run it”)。
- フロントの変更とBFFの変更を、同じリリースサイクルで出せるようにします。
- 可能ならフロントと同じ言語(TypeScript など)を使い、型を共有します。
3.3 ビジネスロジックを持たない
- BFFに置くのは表示のためのロジック(集約、整形、並べ替え、表示用の計算)に限ります。
- 料金計算、在庫の引き当て、権限判定のルールといったドメインロジックは下流サービスに置きます。
- 判断基準として「この処理は別のクライアントでも必要か?」と考え、必要ならバックエンドへ移します。
3.4 BFFは薄く保つ
- 「とりあえずBFFに書く」が積み重なると、肥大化して重複ロジックの温床になります。
- 複数のBFFで同じ処理が重複してきたら、それは下流サービスへ移す(または共通ライブラリにする)サインです。
3.5 画面・ユースケース指向のAPI設計
- エンドポイントは、リソースの形ではなく画面やユースケースの単位で設計します(例:
GET /home、GET /checkout/summary)。
- 画面が必要とする最小限のデータだけを返し、過剰取得(over-fetching)と取得不足(under-fetching)の両方を防ぎます。
4. API設計・データ処理
4.1 アグリゲーション
- 互いに依存しない呼び出しは並列で実行します(
Promise.all / Promise.allSettled など)。
- 依存関係がある呼び出しは最小限にし、呼び出しが何段も直列に連なるのを避けます。
- N+1呼び出しを避けるため、下流にバッチ取得APIを用意するか、DataLoader のような仕組みでまとめます。
// 例: 依存しない呼び出しを並列化し、一部の失敗を許容する
const [user, orders, recommendations] = await Promise.allSettled([
userService.get(userId),
orderService.listRecent(userId),
recommendService.get(userId), // 失敗しても画面は出せる
]);
4.2 部分的な失敗への対応(Graceful Degradation)
- データを必須と任意に分けます。
- 任意データ(レコメンド、バナーなど)の取得に失敗しても、画面全体はエラーにしません。
null やフォールバック値を返し、クライアントが部分的に表示できるようにします。
- 部分的に失敗したことはレスポンスに明示します(例:
meta.partial: true、errors[] フィールド)。
4.3 データ変換
- 下流のデータモデルをそのまま外へ出さず、クライアント向けのDTOに詰め替えます。
- 日付・通貨・ロケールの整形をどこでやるか(BFFかクライアントか)をチームで決め、統一します。
- 下流のAPIが変わっても、BFFが吸収してクライアントに影響が出ないようにします(腐敗防止層として働かせる)。
4.4 REST か GraphQL か
| 観点 | REST BFF | GraphQL BFF |
|---|
| 向いているケース | 画面数が限られ、要件が安定している | 画面が多く、データ要件が頻繁に変わる |
| キャッシュ | HTTPキャッシュ・CDNを使いやすい | 独自の工夫が要る(Persisted Queries など) |
| 注意点 | エンドポイントが増えやすい | クエリの複雑度制限、N+1対策が必須 |
- GraphQLを使う場合は、クエリの深さ・複雑度の制限と Persisted Queries を導入し、任意のクエリを実行させないようにします。
4.5 バージョニングと互換性
- BFFとクライアントは同じチームが一緒にリリースするのが理想ですが、モバイルアプリは古いバージョンが長く残ります。
- モバイルBFFでは後方互換性を保ちます。フィールドは追加のみにし、削除は段階的に行います。
- 必要に応じて、アプリのバージョンやヘッダーを見てレスポンスを切り替えます。
5. セキュリティ
5.1 トークンをサーバー側で持つ(BFFパターンによる認証)
- SPAのOAuth 2.0 / OIDCでは、アクセストークンとリフレッシュトークンをBFFのサーバー側に保持し、ブラウザには
HttpOnly; Secure; SameSite 付きのセッションCookieだけを渡します。
- これにより、XSSでトークンが盗まれるリスクを大きく減らせます。IETF の「OAuth 2.0 for Browser-Based Apps」でも推奨されている構成です。
- BFFは「Confidential Client」として、クライアントシークレットを安全に扱えます。
5.2 CSRF対策
- Cookieで認証する以上、CSRF対策は必須です。
SameSite=Lax または Strict
- 状態を変えるリクエストでは、カスタムヘッダー(例:
X-CSRF-Token)やOriginヘッダーを検証する
5.3 下流サービスへの認証の伝播
- BFFから下流サービスへは、ユーザーのコンテキストを安全に渡します(トークンの中継、トークン交換、内部向けJWTなど)。
- 認可(権限チェック)の最終判断は下流サービスで行います。BFFでのチェックは、表示を制御するための補助と位置づけます。
5.4 その他
- 入力値のバリデーションをBFFの入口でも行います(スキーマバリデーション: Zod、JSON Schema など)。
- 下流のエラーメッセージやスタックトレースを、そのままクライアントに返さないようにします。
- レート制限、CORSの設定、セキュリティヘッダーを適切に設定します。
- 内部APIのURLやシークレットをクライアントに出さないようにします。
6. パフォーマンス
- タイムアウトを必ず設定します。下流の呼び出しごとに設定し、BFF全体のレスポンス時間の予算から逆算して決めます。
- キャッシュ
- ユーザーごとに変わらないデータは、BFFでキャッシュします(インメモリ、Redis など)。
- 公開データには
Cache-Control を付け、CDNを活用します。
- キャッシュキーにユーザー固有の情報が混ざらないよう注意します(他人のデータが漏れる事故を防ぐ)。
- コネクションの再利用(HTTP Keep-Alive、コネクションプール、HTTP/2)を有効にします。
- ペイロードを削減します(不要フィールドの除去、gzip/brotli圧縮、ページネーション)。
- 必要に応じてストリーミング(SSE、React Server Components のストリーミングなど)を使い、最初の表示を早めます。
- BFFは下流サービスと同じリージョン・ネットワークの近くに置き、BFFと下流の間のレイテンシを抑えます。
7. 耐障害性
| 手法 | 目的 |
|---|
| タイムアウト | 遅い下流サービスに引きずられない |
| リトライ(指数バックオフ+ジッター) | 一時的な障害から回復する。冪等な操作に限る |
| サーキットブレーカー | 障害が起きている下流への呼び出しを早めに遮断し、連鎖障害を防ぐ |
| バルクヘッド | 下流ごとにコネクションやスレッドの枠を分け、影響を局所化する |
| フォールバック | キャッシュ値、デフォルト値、空配列などで縮退して動かす |
- BFFは単一障害点になりやすいので、水平スケールできるステートレス構成にします。セッションはRedisなどの外部ストアに置きます。
- ヘルスチェック(liveness / readiness)を実装します。readinessには、必須の下流サービスへの疎通を含めるかどうかを慎重に決めます(含めすぎると連鎖して全滅する)。
8. 可観測性(Observability)
- 分散トレーシング(OpenTelemetry など)を導入し、クライアント → BFF → 下流サービスの流れを1本のトレースで追えるようにします。
- リクエストIDやトレースIDを下流へ伝播させます(W3C Trace Context)。
- 構造化ログ(JSON)を出し、ユーザーID・エンドポイント・下流ごとのレイテンシを記録します。個人情報はマスクします。
- 主要なメトリクス
- エンドポイントごとのレイテンシ(p50 / p95 / p99)、エラー率、スループット
- 下流サービスごとのレイテンシとエラー率
- キャッシュヒット率、サーキットブレーカーの状態
- フロントエンドの体験に近い指標(画面単位のSLO)を定義します。
9. 開発・テスト・運用
9.1 型とスキーマの共有
- OpenAPI、GraphQLスキーマ、tRPC などを使い、BFFとフロントエンドで型を共有します。
- 下流サービスのクライアントコードもスキーマから自動生成し、手書きによる型のずれを防ぎます。
9.2 テスト戦略
| 種類 | 内容 |
|---|
| ユニットテスト | 変換・整形ロジックを中心に検証する |
| コントラクトテスト | 下流サービスとの契約を検証する(Pact など)。下流の変更に気づくうえで特に重要 |
| 統合テスト | 下流をモックやスタブに置き換え、アグリゲーションと障害時の挙動を確認する |
| E2Eテスト | 主要なユーザーフローに絞って行う |
- 部分的な失敗、タイムアウト、遅延といった異常系のテストを重視します。
9.3 デプロイ・運用
- コンテナやサーバーレスでステートレスに動かし、オートスケールを設定します。
- CI/CDで、フロントエンドとBFFを同時に、または整合性を保ってデプロイできるようにします。
- フィーチャーフラグを使って、段階的にリリースします。
10. よくあるアンチパターン
| アンチパターン | 問題 | 対策 |
|---|
| 汎用BFF(One BFF to rule them all) | 全クライアントの要件が混ざり、肥大化する | 体験の単位でBFFを分ける |
| ビジネスロジックの流入 | BFFごとにロジックが重複し、挙動がずれる | ドメインロジックは下流サービスへ |
| 単なるパススルー | 中継するだけでは価値がなく、レイテンシだけが増える | 集約・整形の価値がないならBFFを経由させない |
| BFF同士の呼び出し | 依存関係が複雑になり、障害が連鎖する | BFFは下流サービスだけを呼ぶ |
| BFFが自前のDBを持つ | データの所有権があいまいになる | 持つのはセッションとキャッシュ程度に留める |
| 直列呼び出しの多段化 | レイテンシが積み上がる | 並列化し、下流にバッチAPIを用意する |
| タイムアウトを設定しない | 1つの遅い下流サービスがBFF全体を止める | すべての外部呼び出しにタイムアウトを設定する |
| 認可をBFFだけで行う | 下流サービスを直接呼ばれると権限を回避される | 認可の最終判断は下流で行う |
11. 実装技術の例
| カテゴリ | 例 |
|---|
| フレームワーク | Node.js(Fastify、NestJS、Hono、Express)、Next.js(Route Handlers / Server Actions)、Remix、Go、Kotlin(Spring) |
| GraphQL | Apollo Server、GraphQL Yoga、Mercurius |
| 型共有・API定義 | OpenAPI+コード生成、tRPC、GraphQL Code Generator |
| 認証 | OAuth 2.0 / OIDC クライアントライブラリ、Auth.js、セッションストア(Redis) |
| 耐障害性 | cockatiel、opossum(サーキットブレーカー)、resilience4j |
| 可観測性 | OpenTelemetry、Datadog、Grafana / Prometheus |
| テスト | Vitest / Jest、Pact、MSW |
Next.js や Remix などのフルスタックフレームワークのサーバー側(Server Components、Route Handlers、loader など)は、実質的にBFFとして機能します。別サービスとしてBFFを立てる前に、フレームワークのサーバー層で足りるかを検討します。
12. チェックリスト
設計
セキュリティ
パフォーマンス・耐障害性
運用
参考資料