面接: マイクロサービス間データ集約の設計(密結合なし)
問題¶
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に押し付けることで、注文サービス自体はシンプルに保てる。
解決策4: GraphQL Federation¶
各サービスがGraphQLスキーマを持ち、Federationで1つのAPIとして結合する。
面接でのポイント¶
- まず「タイトな結合の何が問題か」を説明する
- 同期vs非同期のトレードオフを議論できる
- データの一貫性(強整合性 vs 結果整合性)の話が出せる
- 使用するミドルウェア(Kafka、Redis、サーキットブレーカー)の選択理由を言える