マイクロサービス では、クライアントが多数のサービスと直接やり取りすると、 呼び出しの複雑さやサービス境界の変更への追従コストがクライアント側に漏れ出てしまう。これを緩和するために、 クライアントとサービス群の間に仲介層を置くパターン。
API Gateway¶
すべてのクライアントリクエストを受け付ける単一のエントリポイント。以下のような横断的関心事をサービス群から 切り離して一箇所に集約する。
ルーティング(リクエストを適切なサービスへ振り分ける)
認証・認可
レート制限
リクエスト集約(複数サービスへの呼び出しを1回のクライアントリクエストにまとめる)
プロトコル変換(外部向けREST ⇔ 内部向けgRPCなど)
class ApiGateway:
def handle_get_order_detail(self, order_id, auth_token):
self.authenticate(auth_token)
order = order_service.get(order_id)
customer = customer_service.get(order.customer_id) # 複数サービスへの呼び出しを集約
shipping = shipping_service.get_status(order_id)
return {"order": order, "customer": customer, "shipping": shipping}Gatewayが肥大化してすべてのビジネスロジックを持ち始めると、それ自体が新たなモノリス(分散モノリス)になってしまう 点に注意が必要。あくまで横断的関心事とルーティングに徹するのが望ましい。
BFF(Backend for Frontend)¶
単一のAPI Gatewayをすべてのクライアント(Webアプリ、モバイルアプリ、パートナー向けAPIなど)で共有すると、 クライアントごとに異なるデータ形状の要求(モバイルは軽量なレスポンスが欲しい、Web管理画面は詳細なデータが欲しい等) が1つのGatewayに混在し、変更のたびに複数チームの調整が必要になる。
BFFは、クライアントの種類ごとに専用のGateway層を用意する ことでこれを解決する。
[Webアプリ] → [Web BFF] ─┐
[モバイルアプリ] → [Mobile BFF] ─┼─→ [注文サービス] [在庫サービス] [顧客サービス] ...
[パートナーAPI] → [Partner BFF]─┘各BFFはそのクライアント専用のチームが所有し、独立してリリースできる
クライアントに最適化されたレスポンス形状をBFF側で組み立てられる(トランザクションスクリプト 的な「集約役」としてBFFが振る舞うことが多い)
一方でBFFの数だけ横断的関心事(認証など)が重複しやすく、共通ライブラリ化などの対策が必要になる