目次

  1. BFFとは
  2. 導入を判断する基準
  3. 設計原則
  4. API設計・データ処理
  5. セキュリティ
  6. パフォーマンス
  7. 耐障害性
  8. 可観測性(Observability)
  9. 開発・テスト・運用
  10. よくあるアンチパターン
  11. 実装技術の例
  12. チェックリスト
  13. 参考資料

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 BFFGraphQL 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)
GraphQLApollo 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. チェックリスト

設計

  • BFFを体験(クライアント)の単位で分けているか
  • フロントエンドチームが所有・運用しているか
  • ビジネスロジックが入り込んでいないか
  • エンドポイントが画面・ユースケース単位で設計されているか

セキュリティ

  • トークンをサーバー側で持ち、ブラウザには HttpOnly Cookie だけを渡しているか
  • CSRF対策をしているか
  • 認可の最終判断を下流サービスで行っているか
  • 入力値を検証し、内部エラーを隠しているか

パフォーマンス・耐障害性

  • 依存しない呼び出しを並列化しているか
  • すべての下流呼び出しにタイムアウトを設定しているか
  • 任意データの失敗時に縮退して動けるか
  • サーキットブレーカーとリトライ(冪等な操作のみ)を導入しているか
  • ステートレスで水平スケールできるか

運用

  • 分散トレーシングでリクエストを端から端まで追えるか
  • 下流サービスごとのレイテンシとエラー率を監視しているか
  • 下流とのコントラクトテストがあるか
  • フロントエンドと型・スキーマを共有しているか

参考資料

0件のページ