Skip to content
V3.0 // STABLE
LOAD 12%
LAT 24MS
SLA 99.99%

スケーラブルな決済システムの設計

2 min read
51 views
paymentsdistributed systemsarchitecturegolang

決済システムの設計には、高い一貫性、信頼性、セキュリティが求められます。この記事では、エンタープライズ規模での決済処理のための高レベルアーキテクチャを設計し、Idempotency(冪等性)Transactional Integrity(トランザクションの整合性)、**Asynchronous Processing(非同期処理)**に焦点を当てます。

コアエンジニアリング原則

  1. Idempotency(冪等性): すべての決済リクエストは、一意の idempotency_key を持つ必要があります。これにより、ネットワークタイムアウトが発生してクライアントがリトライした場合でも、二重課金が発生しないようにします。
  2. ACID Transactions(ACIDトランザクション): 財務記録は、アトミックで一貫性がある必要があります。台帳の更新には、厳密なロックを備えたリレーショナルデータベース(PostgreSQL/MySQL)を使用します。
  3. Scalable State Machine(スケーラブルなステートマシン): 決済は、PENDINGPROCESSINGSUCCEEDED / FAILED のいくつかの状態を経ます。

高レベルアーキテクチャ

決済システムの高レベルアーキテクチャを以下に示します。

Live architecture
Analyzing Schema...

Arch Note

Interactive logic enabled. Click components in expanded view for technical service definitions.

Layer.0 / Distributed_System_Viz

データベーススキーマ (ERD)

堅牢な決済システムは、監査可能性のための適切に設計されたスキーマから始まります。

Live architecture
Analyzing Schema...

Arch Note

Interactive logic enabled. Click components in expanded view for technical service definitions.

Layer.0 / Distributed_System_Viz

実装: Golang での Idempotency

リレーショナルデータベースにアクセスする前に、Redis を使用してリクエストキーを迅速に保存および検証します。

func (s *PaymentService) ProcessPayment(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error) {
    // 1. Redis で既存の Idempotency Key を確認
    exists, err := s.redis.SetNX(ctx, req.IdempotencyKey, "PROCESSING", 30*time.Minute).Result()
    if err != nil {
        return nil, fmt.Errorf("idempotency check failed: %w", err)
    }
    if !exists {
        return nil, ErrDuplicateRequest
    }
 
    // 2. DB トランザクションでラップ
    err = s.db.WithTransaction(func(tx *sql.Tx) error {
        // ... コア決済ロジック ...
        return nil
    })
 
    if err != nil {
        // ビジネスエラーでリトライ可能な場合は、idempotency をリセット
        s.redis.Del(ctx, req.IdempotencyKey)
        return nil, err
    }
 
    return &PaymentResponse{Status: "SUCCESS"}, nil
}

分散障害の処理

外部プロバイダー(Stripe、銀行)を扱う場合、ローカルDBトランザクションでラップすることはできません。Saga Pattern (Choreography-based) を使用します。

  1. Local Transaction(ローカルトランザクション): DB に PENDING レコードを作成します。
  2. External Call(外部呼び出し): Payment Provider API を呼び出します。
  3. Async Callback(非同期コールバック): Webhook を受信するか、ステータスをポーリングします。
  4. Completion(完了): DB を更新し、Kafka 経由でダウンストリームイベントをトリガーします。

このアーキテクチャにより、ステップ2の後にサーバーがクラッシュした場合でも、保留中のレコードを使用して後で状態を調整できます。

決済フロー

  1. クライアントは、一意の idempotency key を持つ決済リクエストを送信します。
  2. API Gateway は、それを Payment Service にルーティングします。
  3. Payment Service は、この idempotency key がすでに処理されているかどうかをデータベースで確認します。
  4. 処理されていない場合、保留中の決済レコードを作成します。
  5. 外部決済プロバイダーを呼び出します。
  6. 決済が成功すると、データベースを更新し、「Payment Succeeded」イベントを Kafka に発行します。
  7. Notification Service がイベントをピックアップし、ユーザーにメールを送信します。

このアーキテクチャは、信頼性とスケールを保証します。