コンテンツにスキップ

面接: マイクロサービス間データ集約の設計(密結合なし)

問題

1つのマイクロサービスが他の3つのサービスからデータが必要。タイトな結合(tight coupling)なしにどう設計するか?

タイトな結合とは何が問題か

注文サービス
    ↓ 同期HTTP直接呼び出し
    ├── ユーザーサービス.getUser(userId)
    ├── 在庫サービス.getStock(itemId)
    └── 価格サービス.getPrice(itemId)

問題点: - どれか1つが落ちると注文サービス全体が失敗する(カスケード障害) - 全サービスへの呼び出しが完了するまでレスポンスが遅くなる - サービス間の依存関係が強く、独立してデプロイしにくい

解決策1: 並列呼び出し + サーキットブレーカー

同期呼び出しを保ちつつ、障害耐性を高める。

// 3つのサービスを並列で呼ぶ
var (
    user  User
    stock Stock
    price Price
    wg    sync.WaitGroup
    errs  []error
)

wg.Add(3)
go func() {
    defer wg.Done()
    user, err = userClient.GetUser(ctx, userID)
    // サーキットブレーカーがあれば障害時はすぐ失敗
}()
go func() {
    defer wg.Done()
    stock, err = stockClient.GetStock(ctx, itemID)
}()
go func() {
    defer wg.Done()
    price, err = priceClient.GetPrice(ctx, itemID)
}()
wg.Wait()

直列より3倍速い。サーキットブレーカー(Hystrix, Resilience4j)でフォールバック値を返す。

解決策2: イベント駆動 + ローカルキャッシュ(推奨)

各サービスが必要なデータを自分のDBに複製して持つ。

ユーザーサービス → user.updated イベント発行
在庫サービス   → stock.updated イベント発行
価格サービス   → price.updated イベント発行
    ↓ Kafkaなどのメッセージキュー
注文サービス(購読)
    → ローカルDBに最新のuser/stock/priceを保持

注文サービスが注文を作成するとき、外部サービスに問い合わせず ローカルDBから取得 できる。

メリット: - 他サービスがダウンしていても注文処理は動く - レイテンシが小さい(ローカルDBアクセスのみ)

デメリット: - データの一貫性は「結果整合性」(数秒の遅延あり) - データの複製・同期の仕組みが必要

解決策3: API Gateway / BFF(Backend for Frontend)

クライアントがバラバラのサービスを呼ぶのではなく、集約レイヤーを挟む。

クライアント
BFF / API Gateway(集約)
    ├── ユーザーサービス
    ├── 在庫サービス
    └── 価格サービス

集約の責務をBFFに押し付けることで、注文サービス自体はシンプルに保てる。

解決策4: GraphQL Federation

各サービスがGraphQLスキーマを持ち、Federationで1つのAPIとして結合する。

# 注文サービスのスキーマ
type Order {
    id: ID!
    user: User @external  # ← ユーザーサービスから取得
    items: [Item!]!
}

面接でのポイント

  1. まず「タイトな結合の何が問題か」を説明する
  2. 同期vs非同期のトレードオフを議論できる
  3. データの一貫性(強整合性 vs 結果整合性)の話が出せる
  4. 使用するミドルウェア(Kafka、Redis、サーキットブレーカー)の選択理由を言える