LESSON 30分

ストーリー

あなたが設計したマイクロサービスのプロトタイプを見た 高橋アーキテクト が、1つの質問をしました。

高橋アーキテクト
サービス間の通信はすべてREST APIにしたんだね。ところで、注文確定後の在庫更新と通知送信、本当に同期呼び出しで良いのかい?
あなた
え、他に方法があるんですか?
高橋アーキテクト
同期と非同期。この選択が、システムの信頼性とスケーラビリティを左右する最も重要な設計判断の1つだよ

同期通信

リクエストを送信し、レスポンスが返るまで待つ通信方式です。

REST(HTTP)

// REST: 最もシンプルなサービス間通信
interface OrderService {
  // 注文を作成(同期)
  createOrder(data: OrderData): Promise<Order>;
}

// 実装例
async function createOrder(data: OrderData): Promise<Order> {
  // 在庫サービスに同期で確認
  const stock = await fetch("http://inventory-service/check", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ productId: data.productId, quantity: data.quantity }),
  });

  if (!stock.ok) throw new Error("在庫不足");

  // 決済サービスに同期で決済
  const payment = await fetch("http://payment-service/charge", {
    method: "POST",
    body: JSON.stringify({ amount: data.totalAmount }),
  });

  if (!payment.ok) throw new Error("決済失敗");

  return saveOrder(data);
}

gRPC

// gRPC: 高速なバイナリ通信(Protocol Buffers)
// proto定義
// service InventoryService {
//   rpc CheckStock (StockRequest) returns (StockResponse);
// }

import { InventoryServiceClient } from "./generated/inventory_grpc_pb";

const client = new InventoryServiceClient("inventory-service:50051");

async function checkStock(productId: string, quantity: number): Promise<boolean> {
  const request = new StockRequest();
  request.setProductId(productId);
  request.setQuantity(quantity);

  return new Promise((resolve, reject) => {
    client.checkStock(request, (err, response) => {
      if (err) reject(err);
      else resolve(response.getAvailable());
    });
  });
}

REST vs gRPC 比較

観点RESTgRPC
プロトコルHTTP/1.1HTTP/2
データ形式JSON(テキスト)Protocol Buffers(バイナリ)
速度普通高速(5〜10倍)
ストリーミング困難ネイティブサポート
ブラウザ対応完全限定的(gRPC-Web)
デバッグ容易性高い(JSON可読)低い(バイナリ)
型安全性OpenAPIで補完スキーマ定義で保証

非同期通信

メッセージを送信したら、レスポンスを待たずに次の処理に進む通信方式です。

メッセージキュー

// メッセージキューによる非同期通信
interface MessageBroker {
  publish(topic: string, message: unknown): Promise<void>;
  subscribe(topic: string, handler: (message: unknown) => Promise<void>): void;
}

// 注文サービス(Producer)
async function createOrder(data: OrderData): Promise<Order> {
  const order = await saveOrder(data);

  // 非同期でイベントを発行(レスポンスを待たない)
  await messageBroker.publish("order.created", {
    orderId: order.id,
    productId: data.productId,
    quantity: data.quantity,
    userId: data.userId,
  });

  return order; // すぐにレスポンスを返す
}

// 在庫サービス(Consumer)
messageBroker.subscribe("order.created", async (event: OrderCreatedEvent) => {
  await reserveStock(event.productId, event.quantity);
});

// 通知サービス(Consumer)
messageBroker.subscribe("order.created", async (event: OrderCreatedEvent) => {
  await sendOrderConfirmation(event.userId, event.orderId);
});

同期 vs 非同期:判断基準

同期が適切な場面:
  ├─ レスポンスが即座に必要(在庫確認 → 画面表示)
  ├─ 処理結果を待つ必要がある(決済の成否)
  └─ リクエスト-レスポンスの1:1通信

非同期が適切な場面:
  ├─ 結果を待たなくて良い(通知送信)
  ├─ 1つのイベントを複数サービスが処理(1:N通信)
  ├─ 時間のかかる処理(PDF生成、データ集計)
  └─ サービス間の結合度を下げたい
判断基準同期非同期
即時レスポンス必要不要
結合度高い(直接呼び出し)低い(ブローカー経由)
信頼性相手がダウン → 失敗キューにバッファリング
スケーラビリティ制限あり高い
デバッグ容易性高い低い
複雑さ低い高い

ハイブリッドアプローチ

実際のシステムでは、同期と非同期を組み合わせます。

async function processOrder(data: OrderData): Promise<OrderResult> {
  // 同期: 在庫確認(結果が即座に必要)
  const stockAvailable = await inventoryService.checkStock(data.productId);
  if (!stockAvailable) throw new Error("在庫不足");

  // 同期: 決済(結果を待つ必要あり)
  const payment = await paymentService.charge(data.amount);
  if (!payment.success) throw new Error("決済失敗");

  // 注文を保存
  const order = await orderRepository.save(data);

  // 非同期: 後続処理(結果を待たなくて良い)
  await eventBus.publish("order.completed", {
    orderId: order.id,
    userId: data.userId,
  });
  // → 通知サービスがメール送信
  // → 分析サービスが集計更新
  // → 配送サービスが配送手配

  return { orderId: order.id, status: "CONFIRMED" };
}

まとめ

ポイント内容
同期通信REST, gRPC。即時レスポンスが必要な場面
非同期通信メッセージキュー。結合度を下げたい場面
gRPCの利点高速、型安全、ストリーミング対応
ベストプラクティス同期と非同期のハイブリッド

チェックリスト

  • 同期通信と非同期通信の違いを説明できる
  • RESTとgRPCの使い分けを判断できる
  • メッセージキューによる非同期通信を理解した
  • 同期・非同期の選択基準を説明できる

次のステップへ

次はAPI Gatewayとサービスメッシュを学びます。サービスが増えると、クライアントとサービス群の間に交通整理が必要になります。


推定読了時間: 30分