ストーリー
佐藤CTOが強い口調で続けます。
Database per Service
サービス別データベース選定
| サービス | データベース | 選定理由 |
|---|---|---|
| Payment Service | Amazon Aurora PostgreSQL | ACID、トランザクション整合性、PCI DSS対応 |
| Account Service | Amazon Aurora PostgreSQL | 残高管理の強整合性、分別管理 |
| Transfer Service | Amazon Aurora PostgreSQL | 送金のACID、監査証跡 |
| Investment Service | Amazon Aurora PostgreSQL + DynamoDB | 注文/約定はAurora、ポートフォリオはDynamoDB |
| KYC Service | DynamoDB | 書類管理はS3、審査状態はDynamoDB |
| Settlement Service | Aurora PostgreSQL | 精算バッチの集計クエリ |
| Notification Service | DynamoDB | 高スループット、TTL自動削除 |
| Event Store | Amazon MSK (Kafka) | イベントソーシングの永続化 |
// データベース選定の判断マトリクス
interface DatabaseSelectionCriteria {
service: string;
consistency: "STRONG" | "EVENTUAL";
queryPattern: "OLTP" | "OLAP" | "KEY_VALUE" | "TIME_SERIES";
dataVolume: string;
compliance: string[];
decision: string;
rationale: string;
}
const selections: DatabaseSelectionCriteria[] = [
{
service: "Payment",
consistency: "STRONG",
queryPattern: "OLTP",
dataVolume: "10億件/月",
compliance: ["PCI_DSS", "資金決済法"],
decision: "Aurora PostgreSQL",
rationale: "決済の原子性保証。PCI DSS Req-3のデータ保護。パーティショニングで10億件に対応",
},
{
service: "Account",
consistency: "STRONG",
queryPattern: "OLTP",
dataVolume: "1,000万ユーザー",
compliance: ["資金決済法"],
decision: "Aurora PostgreSQL",
rationale: "残高管理の厳密なトランザクション。SELECT FOR UPDATEによる排他制御",
},
];
イベントソーシング
決済サービスのイベントソーシング設計
金融取引では完全な監査証跡が法的義務です。イベントソーシングにより、すべての状態変更をイミュータブルなイベントとして記録します。
// 決済ドメインイベント
type PaymentEvent =
| PaymentInitiated
| PaymentAuthorized
| PaymentCaptured
| PaymentRefunded
| PaymentCancelled
| PaymentFailed;
interface PaymentInitiated {
type: "PaymentInitiated";
paymentId: string;
merchantId: string;
amount: Money;
paymentMethod: PaymentMethod;
occurredAt: Date;
initiatedBy: string; // 監査: 誰が開始したか
}
interface PaymentAuthorized {
type: "PaymentAuthorized";
paymentId: string;
authorizationCode: string;
cardNetworkResponse: string;
occurredAt: Date;
}
interface PaymentCaptured {
type: "PaymentCaptured";
paymentId: string;
capturedAmount: Money;
settlementDate: Date;
occurredAt: Date;
}
// イベントストアへの保存
class PaymentEventStore {
async append(
aggregateId: string,
events: PaymentEvent[],
expectedVersion: number,
): Promise<void> {
// 楽観的ロック: expectedVersionで競合を検出
const currentVersion = await this.getVersion(aggregateId);
if (currentVersion !== expectedVersion) {
throw new OptimisticLockError(aggregateId, expectedVersion, currentVersion);
}
// イベントを追記(イミュータブル。更新・削除は不可)
await this.store.appendEvents(aggregateId, events, expectedVersion + 1);
// イベントをKafkaにパブリッシュ
await this.eventBus.publish(events);
}
// 集約の状態を再構築
async loadAggregate(aggregateId: string): Promise<Payment> {
const events = await this.store.getEvents(aggregateId);
return Payment.fromEvents(events);
}
}
イベントストアのデータモデル
-- イベントストアテーブル(Append Only)
CREATE TABLE payment_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_id UUID NOT NULL,
aggregate_type VARCHAR(50) NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_data JSONB NOT NULL,
metadata JSONB NOT NULL, -- トレースID、ユーザーID等
version INTEGER NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- 楽観的ロック用のユニーク制約
UNIQUE (aggregate_id, version)
) PARTITION BY RANGE (occurred_at);
-- 月次パーティション(10億件/月に対応)
CREATE TABLE payment_events_2026_01
PARTITION OF payment_events
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
-- インデックス
CREATE INDEX idx_payment_events_aggregate
ON payment_events (aggregate_id, version);
CQRS の適用
読み取り最適化
イベントソーシングで書き込みを最適化した一方、読み取りは別のモデル(リードモデル)で最適化します。
graph LR
subgraph Write["Write Side - Command"]
WR["Payment Aggregate
Event Store
Aurora PG
Append Only"]
WP["強整合性
書き込み最適化
10,000 TPS"]
end
subgraph Read["Read Side - Query"]
RD["Payment Read Model
Materialized View
ElastiCache + DynamoDB"]
RP["結果整合性
読み取り最適化
50,000 RPS"]
end
WR -- "イベント
Projection" --> RD
style Write fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style Read fill:#d1fae5,stroke:#059669,color:#065f46
style WR fill:#dbeafe,stroke:#2563eb,color:#1e40af
style RD fill:#d1fae5,stroke:#059669,color:#065f46
style WP fill:#f3f4f6,stroke:#9ca3af,color:#374151
style RP fill:#f3f4f6,stroke:#9ca3af,color:#374151
プロジェクション(読み取りモデル生成)
// 決済の読み取りモデルを生成するプロジェクション
class PaymentReadModelProjection {
async handle(event: PaymentEvent): Promise<void> {
switch (event.type) {
case "PaymentInitiated":
await this.readStore.create({
paymentId: event.paymentId,
merchantId: event.merchantId,
amount: event.amount,
status: "INITIATED",
createdAt: event.occurredAt,
});
break;
case "PaymentCaptured":
await this.readStore.update(event.paymentId, {
status: "CAPTURED",
capturedAt: event.occurredAt,
});
// キャッシュも更新
await this.cache.set(
`payment:${event.paymentId}`,
await this.readStore.get(event.paymentId),
{ ttl: 3600 },
);
break;
case "PaymentRefunded":
await this.readStore.update(event.paymentId, {
status: "REFUNDED",
refundedAt: event.occurredAt,
});
break;
}
}
}
// 残高照会用の特化読み取りモデル
class BalanceReadModelProjection {
// 残高はRedisに保持(50,000 RPSの読み取り性能)
async handleBalanceChange(event: BalanceChangedEvent): Promise<void> {
await this.redis.set(
`balance:${event.userId}`,
JSON.stringify({
available: event.newBalance,
pending: event.pendingAmount,
updatedAt: event.occurredAt,
}),
);
}
}
データ暗号化戦略
暗号化レイヤー
暗号化戦略:
転送中(In Transit):
外部通信: TLS 1.3
内部通信: mTLS(サービスメッシュ)
データベース接続: SSL/TLS
保存時(At Rest):
データベース: Aurora暗号化(AES-256、AWS KMS)
S3: SSE-KMS
EBS: AES-256暗号化
バックアップ: 暗号化された状態で保存
アプリケーションレベル:
カード番号: トークン化(Vaultサービス)
個人情報(PII): フィールドレベル暗号化(AES-256-GCM)
暗号鍵: AWS KMS + HSM(PCI DSS準拠)
トークン化サービス
// カード番号のトークン化
class TokenizationService {
private readonly hsm: HSMClient; // Hardware Security Module
async tokenize(cardNumber: string): Promise<string> {
// カード番号をHSMで暗号化し、トークンを返す
const token = await this.hsm.encrypt(cardNumber);
// マッピングをCDE内のVaultに保存
await this.vault.store(token, cardNumber);
return token; // "tok_4242424242424242" のような形式
}
// CDE内でのみ使用可能
async detokenize(token: string): Promise<string> {
return await this.vault.retrieve(token);
}
}
「イベントソーシング + CQRS + トークン化。この3つの組み合わせが、フィンテックのデータアーキテクチャの核心だ。完全な監査証跡、読み書きの独立スケーリング、そしてカード情報の保護を同時に実現する」 — 佐藤CTO
データのライフサイクル管理
| データ種別 | ホットストレージ | ウォームストレージ | コールドストレージ | 保持期間 |
|---|---|---|---|---|
| 決済イベント | Aurora(直近3ヶ月) | S3 Glacier IR(3ヶ月-1年) | S3 Glacier Deep(1-10年) | 10年 |
| 残高スナップショット | Redis(最新) | Aurora(直近1年) | S3(1年以上) | 永久 |
| 監査ログ | CloudWatch(30日) | S3(30日-1年) | S3 Glacier(1-7年) | 7年 |
| KYC書類 | S3(審査中) | S3 IA(審査完了後) | S3 Glacier(5年以上) | 永久 |
| 分析データ | Redshift(直近6ヶ月) | S3 Parquet(6ヶ月以上) | - | 5年 |
まとめ
| ポイント | 内容 |
|---|---|
| Database per Service | サービスごとにDB選定。決済はAurora PG |
| イベントソーシング | 決済の全状態変更をイミュータブルに記録 |
| CQRS | 書き込み(Event Store)と読み取り(Redis/DynamoDB)の分離 |
| 暗号化 | 3層暗号化(転送中/保存時/アプリケーション) |
| トークン化 | カード番号はCDE外でトークンとして流通 |
| ライフサイクル | ホット→ウォーム→コールドの段階的データ管理 |
チェックリスト
- サービスごとのデータベース選定理由を理解した
- イベントソーシングの仕組みと監査証跡の重要性を把握した
- CQRSの読み書き分離と各モデルの役割を理解した
- PCI DSS準拠のデータ暗号化戦略を理解した
- データのライフサイクル管理計画を把握した
次のステップへ
次は「インフラストラクチャ設計」に進みます。マルチリージョンのAWS構成、Kubernetesクラスタトポロジー、災害復旧の設計を行いましょう。
推定読了時間: 40分