EXERCISE 90分

ストーリー

高橋アーキテクト が最終課題を示しました。

高橋アーキテクト
月5の集大成だ。配車サービス(タクシー配車アプリ)の分散アーキテクチャを設計してほしい。マイクロサービス分割、イベント駆動、Saga、CQRSのすべてを統合して、実用的な設計を描いてくれ
あなた
これまでの全スキルを使うんですね
高橋アーキテクト
そう。分散の嵐を乗りこなせるか、ここで試される

ミッション概要

項目内容
題材配車サービス(タクシー配車アプリ)
目標Month 5で学んだ全スキルを統合した分散システム設計
所要時間90分
成果物完全なアーキテクチャ設計書

システム要件

機能要件

  1. 乗客機能: 配車リクエスト、乗車地/目的地指定、運賃見積もり、乗車中の追跡
  2. ドライバー機能: 配車リクエストの受諾/拒否、位置情報の送信、乗車完了報告
  3. マッチング: 最寄りのドライバーを自動マッチング
  4. 決済: 乗車完了後の自動決済
  5. 評価: 乗客とドライバーの相互評価
  6. 通知: 配車確定、ドライバー到着、乗車完了の通知

非機能要件

項目要件
レイテンシ配車リクエスト → マッチング: 5秒以内
スループットピーク時 10,000リクエスト/分
可用性99.9%
データ整合性決済は二重課金を絶対に防ぐ
位置情報ドライバー位置はリアルタイム更新(1秒ごと)

ミッション 1: マイクロサービス分割(15分)

配車サービスを適切なマイクロサービスに分割してください。

設計すべきこと:

  1. サービス一覧と各サービスの責務
  2. 各サービスが所有するデータ
  3. 技術スタック(言語、DB)の選定理由
ヒントと模範回答
サービス分割:

1. Passenger Service
   責務: 乗客のプロフィール管理、認証
   データ: passengers, payment_methods
   技術: TypeScript + PostgreSQL

2. Driver Service
   責務: ドライバーのプロフィール、車両情報
   データ: drivers, vehicles, documents
   技術: TypeScript + PostgreSQL

3. Ride Service
   責務: 乗車のライフサイクル管理
   データ: rides, ride_status_history
   技術: TypeScript + PostgreSQL(Saga Orchestrator)

4. Matching Service
   責務: ドライバーと乗客のマッチング
   データ: matching_requests, match_results
   技術: Go + Redis(高速位置検索)

5. Location Service
   責務: リアルタイム位置情報管理
   データ: driver_locations (揮発性)
   技術: Go + Redis GEO(地理空間インデックス)

6. Pricing Service
   責務: 運賃計算(距離、時間、需要に基づく動的価格)
   データ: pricing_rules, surge_zones
   技術: TypeScript + DynamoDB

7. Payment Service
   責務: 決済処理、返金
   データ: payments, refunds, receipts
   技術: Java + PostgreSQL(高い信頼性)

8. Rating Service
   責務: 評価管理
   データ: ratings, reviews
   技術: TypeScript + DynamoDB

9. Notification Service
   責務: プッシュ通知、SMS
   データ: notification_logs
   技術: TypeScript + SQS

ミッション 2: 通信設計とイベントフロー(15分)

各サービス間の通信方式(同期/非同期)とイベントフローを設計してください。

設計すべきこと:

  1. 同期通信が必要な箇所と理由
  2. 非同期通信が適切な箇所と理由
  3. 主要なイベントの一覧とスキーマ
ヒントと模範回答
同期通信:
  乗客 → Pricing Service: 運賃見積もり(即座に結果が必要)
  Ride Service → Matching Service: マッチングリクエスト(5秒以内)
  Ride Service → Location Service: ドライバー位置の取得

非同期通信(イベント):
  ride.requested      → Matching Serviceがマッチング開始
  ride.matched        → Notification Serviceが乗客・ドライバーに通知
  ride.started        → Location Serviceが追跡開始
  ride.completed      → Payment Serviceが決済開始
                      → Rating Serviceが評価リクエスト
  payment.completed   → Notification Serviceが領収書送信
  payment.failed      → Ride Serviceが決済リトライ

メッセージブローカー: Apache Kafka
  Topics:
  - ride-events (key: rideId, partitions: 24)
  - payment-events (key: rideId, partitions: 12)
  - location-events (key: driverId, partitions: 48)
  - notification-events (partitions: 12)

ミッション 3: Saga設計(配車→乗車→決済フロー)(20分)

配車リクエストから決済完了までのSagaを設計してください。

設計すべきこと:

  1. Sagaのタイプ選択(Choreography / Orchestration)と理由
  2. 各ステップの正常系と補償処理
  3. タイムアウトと障害時の対応
ヒントと模範回答
// Orchestration を選択
// 理由: ステップが多く、タイムアウト管理が重要。
//        乗車のライフサイクル全体を1箇所で管理したい。

const rideSagaSteps: SagaStep[] = [
  {
    name: "createRide",
    execute: async (ctx) => {
      const ride = await rideService.create({
        passengerId: ctx.passengerId,
        pickup: ctx.pickup,
        destination: ctx.destination,
      });
      return { rideId: ride.id };
    },
    compensate: async (ctx) => {
      await rideService.cancel(ctx.rideId, "SYSTEM_CANCELLED");
    },
    timeout: 5000,
  },
  {
    name: "estimatePrice",
    execute: async (ctx) => {
      const estimate = await pricingService.estimate({
        pickup: ctx.pickup,
        destination: ctx.destination,
      });
      return { estimatedFare: estimate.fare };
    },
    compensate: async () => { /* 読み取りのみ、補償不要 */ },
    timeout: 3000,
  },
  {
    name: "matchDriver",
    execute: async (ctx) => {
      const match = await matchingService.findDriver({
        rideId: ctx.rideId,
        pickup: ctx.pickup,
        maxWaitSeconds: 30,
      });
      return { driverId: match.driverId };
    },
    compensate: async (ctx) => {
      await matchingService.releaseDriver(ctx.driverId);
    },
    timeout: 30000, // マッチングは最大30秒待つ
  },
  {
    name: "confirmRide",
    execute: async (ctx) => {
      await rideService.confirm(ctx.rideId, ctx.driverId);
      // → ride.matched イベントが発行される
      return {};
    },
    compensate: async (ctx) => {
      await rideService.cancel(ctx.rideId, "MATCH_CANCELLED");
    },
    timeout: 5000,
  },
  // 乗車完了後(ドライバーがride.completedをトリガー)
  {
    name: "processPayment",
    execute: async (ctx) => {
      const payment = await paymentService.charge({
        rideId: ctx.rideId,
        passengerId: ctx.passengerId,
        amount: ctx.finalFare,
        idempotencyKey: `ride-${ctx.rideId}-payment`,
      });
      return { paymentId: payment.id };
    },
    compensate: async (ctx) => {
      if (ctx.paymentId) {
        await paymentService.refund(ctx.paymentId);
      }
    },
    timeout: 10000,
  },
  {
    name: "notifyCompletion",
    execute: async (ctx) => {
      await notificationService.send(ctx.passengerId, {
        type: "RIDE_COMPLETED",
        rideId: ctx.rideId,
        fare: ctx.finalFare,
      });
      return {};
    },
    compensate: async () => { /* 通知は不可逆、最後に配置 */ },
    timeout: 5000,
  },
];

ミッション 4: CQRS設計(位置情報とライド履歴)(15分)

位置情報のリアルタイム表示と乗車履歴の表示をCQRSで設計してください。

設計すべきこと:

  1. Write Model(位置情報の書き込み、乗車記録)
  2. Read Model(地図上のドライバー表示、乗車履歴画面)
  3. プロジェクションの設計
ヒントと模範回答
// Write Model: ドライバー位置(1秒ごとに更新)
// → Redis GEO(揮発性OK、Write性能最優先)
class LocationWriteService {
  async updateLocation(driverId: string, lat: number, lng: number): Promise<void> {
    await this.redis.geoadd("driver_locations", lng, lat, driverId);
    // Outbox不要: 位置情報はロスト許容(次の更新で上書き)
  }
}

// Read Model 1: 周辺ドライバーの地図表示
// → Redis GEO から直接クエリ(Write = Read、分離不要)
class NearbyDriversQuery {
  async find(lat: number, lng: number, radiusKm: number): Promise<Driver[]> {
    return await this.redis.georadius("driver_locations", lng, lat, radiusKm, "km");
  }
}

// Write Model: 乗車記録
// → PostgreSQL(ACID、正規化)
// Read Model: 乗車履歴画面
// → DynamoDB(userId で高速クエリ)
interface RideHistoryReadModel {
  passengerId: string;
  rideId: string;
  driverName: string;
  driverPhoto: string;
  vehicleInfo: string;
  pickup: string;
  destination: string;
  fare: number;
  rating: number;
  date: string;
}

// プロジェクション: ride.completed → 乗車履歴Read Model更新
class RideHistoryProjection {
  async handleRideCompleted(event: RideCompletedEvent): Promise<void> {
    const driver = await this.driverService.getDriver(event.data.driverId);
    await this.dynamodb.put({
      TableName: "ride_history",
      Item: {
        passengerId: event.data.passengerId,
        rideId: event.data.rideId,
        driverName: driver.name,
        driverPhoto: driver.photoUrl,
        vehicleInfo: `${driver.vehicle.make} ${driver.vehicle.model}`,
        pickup: event.data.pickupAddress,
        destination: event.data.destinationAddress,
        fare: event.data.fare,
        date: event.metadata.timestamp,
      },
    });
  }
}

ミッション 5: テスト・運用戦略(10分)

このシステムのテスト戦略と運用戦略を設計してください。

ヒントと模範回答
テスト戦略:

1. Contract Tests
   - Ride Service ↔ Payment Service の決済API契約
   - Ride Service ↔ Matching Service のマッチングAPI契約
   - イベントスキーマ(ride.completed等)の契約

2. Chaos Engineering
   実験1: Matching Service停止
     仮説: 配車リクエストがタイムアウトし「ドライバーが見つかりません」表示
     検証: 乗客UIにエラーメッセージ、データ不整合なし

   実験2: Payment Service 高遅延(10秒)
     仮説: 乗車完了は即座に表示、決済は非同期で再処理
     検証: 決済が最終的に完了、二重課金なし

   実験3: Kafka障害
     仮説: Outboxパターンでイベントが保持され、復旧後に処理
     検証: イベントのロストなし

3. オブザーバビリティ
   - 分散トレーシング: rideIdをcorrelation IDとして全サービスで追跡
   - メトリクス: マッチング所要時間、決済成功率、ドライバー稼働率
   - ログ: 構造化ログ + ELKで集約

ミッション 6: アーキテクチャ全体図(15分)

すべてを統合したアーキテクチャ図をテキストで描いてください。

ヒントと模範回答
                    [Mobile App (Passenger)]    [Mobile App (Driver)]
                              │                        │
                         [API Gateway]                 │
                         ├─ 認証 (JWT)                 │
                         ├─ レート制限                  │
                         └─ WebSocket (位置情報)        │
                              │                        │
           ┌──────────────────┼────────────────────────┘
           │                  │
      [Passenger]        [Ride Service]            [Driver]
      [Service]          (Saga Orchestrator)        [Service]
           │               │    │    │                │
           │          同期  │    │    │ 同期           │
           │    ┌──────────┘    │    └──────────┐     │
           │    │               │               │     │
      [Pricing]          [Matching]         [Location]
      [Service]          [Service]          [Service]
                              │             [Redis GEO]

                         [Kafka]
                    ┌────────┼────────┐
                    │        │        │
              [Payment]  [Rating]  [Notification]
              [Service]  [Service] [Service]

      Write DB: PostgreSQL (rides, payments)
      Read DB:  DynamoDB (乗車履歴), Redis (位置, マッチング)
      イベント: Kafka (ride-events, payment-events, location-events)
      監視: OpenTelemetry + Jaeger + Prometheus + Grafana

達成チェックリスト

  • 8つ以上のマイクロサービスに適切に分割できた
  • 同期/非同期通信とイベントフローを設計できた
  • 配車→決済のSagaをOrchestrationで設計できた
  • 位置情報と乗車履歴のCQRSを設計できた
  • テスト・運用戦略(Contract Testing, Chaos Engineering)を策定できた
  • 全体のアーキテクチャ図を描けた

推定所要時間: 90分