ストーリー
佐藤CTOが続けます。
DDDベースのサービス分解
ドメインイベントストーミング結果
イベントストーミングで洗い出したドメインイベントから、サービス境界を導出します。
決済ドメイン: 送金ドメイン:
決済リクエスト受信 送金リクエスト受信
与信確認完了 送金元残高確認完了
決済承認完了 送金実行完了
売上確定完了 送金受取通知送信
加盟店入金完了 銀行API送金完了
ポイント付与完了
投資ドメイン: アカウントドメイン:
注文受付完了 ユーザー登録完了
約定完了 KYC審査完了
ポートフォリオ更新完了 残高更新完了
配当受取完了 ポイント残高更新完了
つみたて実行完了 出金リクエスト受信
サービス一覧
| サービス名 | 境界づけられたコンテキスト | 責務 | 担当チーム |
|---|---|---|---|
| Payment Service | 決済コンテキスト | QR/NFC/オンライン決済の処理 | 決済チーム(12名) |
| Settlement Service | 精算コンテキスト | 加盟店精算、入金管理 | 決済チーム |
| Transfer Service | 送金コンテキスト | P2P送金、銀行振込 | 送金チーム(8名) |
| Investment Service | 投資コンテキスト | 株式/投信の注文・約定 | 投資チーム(8名) |
| Account Service | アカウントコンテキスト | ユーザー管理、残高管理 | 共通基盤チーム(8名) |
| KYC Service | 本人確認コンテキスト | KYC/AML処理 | 共通基盤チーム |
| Notification Service | 通知コンテキスト | プッシュ通知、メール、SMS | 共通基盤チーム |
| API Gateway | 外部公開コンテキスト | 認証、ルーティング、レート制限 | SREチーム(6名) |
C4 Level 2: Container Diagram
graph TD
subgraph Clients["クライアント"]
MOB["Mobile App<br/>(iOS/Android)"]
WEB["Merchant Dashboard<br/>(Web)"]
end
MOB --> GW["API Gateway<br/>(Kong/Envoy)"]
WEB --> GW
subgraph Mesh["Service Mesh (Istio/Linkerd)"]
PAY["Payment<br/>Service"] <--> ACC["Account<br/>Service"]
ACC <--> TRF["Transfer<br/>Service"]
SET["Settlement<br/>Service"]
KYC["KYC<br/>Service"]
INV["Investment<br/>Service"]
end
GW --> Mesh
NOTIF["Notification<br/>Service"]
subgraph EventBus["Event Bus (Kafka)"]
KAFKA["Kafka"]
end
subgraph DataStores["データストア"]
PDB["Payment DB<br/>(Aurora)"]
ADB["Account DB<br/>(Aurora)"]
TDB["Transfer DB<br/>(Aurora)"]
IDB["Invest DB<br/>(Aurora)"]
end
PAY --> PDB
ACC --> ADB
TRF --> TDB
INV --> IDB
classDef client fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef gateway fill:#e67e22,stroke:#d35400,color:#fff
classDef service fill:#3498db,stroke:#2980b9,color:#fff
classDef infra fill:#27ae60,stroke:#1e8449,color:#fff
classDef db fill:#2c3e50,stroke:#1a252f,color:#fff
class MOB,WEB client
class GW gateway
class PAY,ACC,TRF,SET,KYC,INV,NOTIF service
class KAFKA infra
class PDB,ADB,TDB,IDB db
サービス間通信の設計
同期 vs 非同期の判断基準
| 通信パターン | 採用基準 | NexPayでの適用例 |
|---|---|---|
| 同期(REST/gRPC) | リアルタイム応答が必要、強整合性 | 決済処理、残高照会、KYC確認 |
| 非同期(イベント) | 結果整合性で十分、デカップリング | ポイント付与、通知送信、精算処理 |
| コレオグラフィ | サービス間の疎結合が重要 | 決済完了→ポイント付与→通知 |
| オーケストレーション | 複雑なワークフロー管理 | 送金フロー(KYC→残高確認→送金→通知) |
決済フローの詳細設計
// 決済フロー: 同期 + 非同期のハイブリッド
interface PaymentFlow {
// Phase 1: 同期処理(ユーザー待機)
synchronousPhase: {
step1: "API Gateway → Payment Service: 決済リクエスト受信";
step2: "Payment Service → Account Service: 残高/与信確認(gRPC、50ms以内)";
step3: "Payment Service → Card Network: 与信承認(ISO 8583、200ms以内)";
step4: "Payment Service → Account Service: 残高引落(gRPC、50ms以内)";
step5: "Payment Service → API Gateway: 決済完了レスポンス";
totalLatency: "p99 < 800ms";
};
// Phase 2: 非同期処理(バックグラウンド)
asynchronousPhase: {
event1: "PaymentCompleted → PointService: ポイント付与";
event2: "PaymentCompleted → NotificationService: 決済通知送信";
event3: "PaymentCompleted → SettlementService: 精算データ蓄積";
event4: "PaymentCompleted → AnalyticsService: 分析データ送信";
event5: "PaymentCompleted → AMLMonitor: 不正検知チェック";
};
}
Saga パターンによる送金フロー
graph LR
KYC["1. KYC確認"] --> BAL["2. 残高確認"]
BAL --> TRF["3. 送金実行"]
TRF --> NTF["4. 通知送信"]
KYC -- "失敗" --> REJ1["送金拒否
理由通知"]
BAL -- "失敗" --> REJ2["送金拒否
残高不足通知"]
TRF -- "失敗" --> COMP["補償TX
残高戻し"]
style KYC fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style BAL fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style TRF fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style NTF fill:#d1fae5,stroke:#059669,color:#065f46
style REJ1 fill:#fee2e2,stroke:#dc2626,color:#991b1b
style REJ2 fill:#fee2e2,stroke:#dc2626,color:#991b1b
style COMP fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
サービスのAPI設計
API設計原則
API設計原則:
バージョニング: URLパスベース(/v1/payments)
認証: OAuth 2.0 + JWT(内部はサービスメッシュのmTLS)
レート制限: サービスごとに個別設定
冪等性: すべての書き込みAPIに冪等性キーを要求
エラーレスポンス: RFC 7807(Problem Details)準拠
ページネーション: カーソルベース(大量データ対応)
主要APIのインターフェース
// Payment API
interface PaymentAPI {
// 決済作成(冪等性キー必須)
createPayment(request: {
idempotencyKey: string;
merchantId: string;
amount: Money;
paymentMethod: PaymentMethod;
metadata?: Record<string, string>;
}): Promise<PaymentResponse>;
// 決済照会
getPayment(paymentId: string): Promise<Payment>;
// 決済取消(冪等性キー必須)
cancelPayment(paymentId: string, request: {
idempotencyKey: string;
reason: CancelReason;
}): Promise<CancelResponse>;
}
// 冪等性の実装
class IdempotencyGuard {
async execute<T>(
key: string,
operation: () => Promise<T>,
): Promise<T> {
const existing = await this.cache.get(key);
if (existing) {
return existing as T; // 同じレスポンスを返す
}
const result = await operation();
await this.cache.set(key, result, { ttl: 86400 }); // 24h保持
return result;
}
}
「決済APIの冪等性は絶対に妥協できない。ネットワーク障害でリトライされた場合に二重決済が発生したら、ユーザーの信頼を完全に失う。すべての書き込みAPIに冪等性キーを要求する設計にしろ」 — 佐藤CTO
チーム構成とサービスの整合(逆コンウェイ)
チーム-サービスマッピング
コンウェイの法則を意識し、チーム構成とサービス境界を整合させます。
| チーム | 担当サービス | オーナーシップ | 認知負荷 |
|---|---|---|---|
| 決済チーム(12名) | Payment, Settlement | Full ownership | 中 |
| 送金チーム(8名) | Transfer | Full ownership | 低 |
| 投資チーム(8名) | Investment | Full ownership | 中 |
| 共通基盤チーム(8名) | Account, KYC, Notification | Full ownership | 高 |
| SREチーム(6名) | API Gateway, Event Bus, Infra | Platform team | 高 |
共通基盤チームの認知負荷対策
共通基盤チームは3サービスを担当しており認知負荷が高いため、以下の対策を実施します。
- Account Serviceを最優先に開発(他チームの依存先)
- KYC Serviceは外部サービスのラッパーとして薄く実装
- Notification Serviceはイベント駆動で疎結合に設計し、メンテナンス負荷を最小化
- チーム拡大時(60名規模)にNotification Serviceを独立チームに移管
まとめ
| ポイント | 内容 |
|---|---|
| サービス分解 | DDDの境界づけられたコンテキストに基づく8サービス |
| 通信設計 | 同期(リアルタイム処理)+ 非同期(イベント駆動)のハイブリッド |
| Saga | 送金フローはオーケストレーション型Sagaで整合性を担保 |
| API設計 | 冪等性キー必須、OAuth 2.0、RFC 7807エラー形式 |
| チーム整合 | 逆コンウェイの法則でチームとサービス境界を一致 |
チェックリスト
- 8サービスの責務と境界を理解した
- 同期/非同期の使い分け判断基準を把握した
- 決済フローの同期+非同期ハイブリッド設計を理解した
- Sagaパターンによる分散トランザクション管理を理解した
- チーム構成とサービス境界の整合を確認した
次のステップへ
次は「データアーキテクチャ設計」に進みます。各サービスのデータベース選定、イベントソーシング、CQRSの適用を設計しましょう。
推定読了時間: 40分