メインコンテンツへスキップ
友田 陽大

環境分野のサーバーレス決済プラットフォーム(フルスタック開発・決済信頼性レイヤーを主導)

顧客・加盟店・管理・店頭端末の4面(バックエンド/フロント)+共有基盤・CI/CD・可観測性/DR/IAMを横断実装|冪等性・原子的トランザクション・ゼロダウンタイム移行で本番二重課金0件を達成

クライアント

環境・カーボンクレジット(Jクレジット)/地域通貨・ポイント・電子商取引を扱うマルチテナント決済プラットフォーム(AWSサーバーレス基盤)|開発体制: チーム開発(主要開発者3名)。私はリポジトリのコミット数の約6割(694中403)を担当する中核エンジニアとして、顧客・加盟店・管理・店頭端末の各フロントエンド/バックエンドと共有基盤・インフラ(可観測性・DR・IAM・CI/CD)を横断的に実装し、特に決済信頼性レイヤー(冪等性・原子的残高更新・ゼロダウンタイム移行)を設計・主導。

私の役割

チーム開発(主要開発者3名)の中核エンジニアとして、リポジトリのコミット数の約6割(694中403)を担当。顧客向けアプリ(React 19 / Vite / MUI / TanStack Query)と加盟店・管理の各フロントエンド、社内向けダッシュボード(Next.js / Mantine)、4つのPythonサーバーレスバックエンド(顧客・加盟店・管理・店頭端末/AWS SAM)、共有Lambda Layer、認証(Cognitoカスタム認証・カードPIN)、可観測性(CloudWatchアラーム・Slack通知・構造化ログ)、DR(AWS Backup・Vault Lock・PITR)、IAM、CI/CD(GitHub Actions・mypy strict)まで横断的に実装。とりわけ決済信頼性レイヤー(冪等性・原子的残高更新・mirror writesによるゼロダウンタイム移行)を設計・主導した。

課題(Situation & Task)

実際の金銭・ポイント・カーボンクレジット(Jクレジット)・地域通貨を、顧客アプリ・加盟店・管理・店頭端末という複数の利用者面にまたがって扱うマルチテナント決済基盤を、サーバーレスで構築・運用する必要がありました。決済では二重課金や残高の不整合が決して許されず、かつ本番を一切止めずにデータモデルを進化させ続けなければなりません。さらに本番運用に耐える可観測性・回復性(DR)・セキュリティを同時に満たす必要がありました。「正しさ」を運用の注意深さではなく、コードの構造とDynamoDBの一貫性プリミティブで保証することが、設計全体の要件でした。

金融グレードの正確性・無停止での進化・本番運用品質という、相反しがちな要件を同時に満たす必要がありました。

  1. マルチサーフェス × マルチ価値: 4つの利用者面(顧客/加盟店/管理/店頭端末)× 複数の価値(現金相当・ポイント・Jクレジット・地域通貨)を、一貫した残高・取引モデルで扱う必要がありました。重複実装は不整合の温床になるため、共通ロジックの一元化(SSoT)が不可欠でした。

  2. 二重課金の排除(Exactly-once): モバイル回線のタイムアウトやLambda/API Gatewayの再試行により、同一の決済リクエストが複数回到達します。リトライ自体は正常系として受け入れつつ、課金は1回だけに収束させる必要がありました。

  3. 競合下の残高整合性(Atomicity): 同一カード・同一顧客への同時操作(決済・チャージ・返金)が並行しても、残高がマイナスに沈んだり二重に減算されたりしてはいけません。典型的なread-modify-writeはレースを生むため、構造的に排除する必要がありました。

  4. ゼロダウンタイム移行: 残高・ポイント・Jクレジット・プロフィールが1つの巨大レコード(God Record)に同居する旧モデルを、関心ごとに分割した新スキーマへ、本番を止めずに移行する必要がありました。

  5. 本番運用品質: 障害の予兆を検知する可観測性、データ損失に備えるDR(バックアップ・復旧)、PIN・個人情報を守るセキュリティを、機能と同等の優先度で作り込む必要がありました。

技術選定の理由(Rationale)

  • AWS Lambda + DynamoDB + SAM(サーバーレス × IaC): 需要に追従しつつサーバー運用負荷を抑える決済基盤として採用。4スタックすべてをAWS SAM(CloudFormation)でコード化し、再現性とレビュー可能性を担保

  • 共有Lambda Layer(SSoT / DRY): 残高・冪等性・取引履歴・認証属性などの中核ロジックを、顧客/加盟店/管理/端末の4バックエンドから共通利用するため、共有Layerに一元化。「1箇所直せば全Lambdaに反映」される構造で不整合と重複を排除

  • DynamoDB TransactWriteItems(atomic ADD + 条件式): ロックを取らずに競合へ強い残高更新を実現するため採用。ADDの原子的増減とConditionExpression(残高下限・チャージ上限)でread-modify-writeのレースを設計時点で排除

  • 純粋関数によるトランザクション・ビルダー: 副作用(DB I/O)を持たずTransactItemのdictだけを返す設計にし、DynamoDB不要で決済ロジックを単体テスト・ゴールデンベクタ固定できるようにするため採用

  • 冪等性キー + attribute_not_exists + TTL: クライアント発行キーをソートキーに連結し条件付き挿入で二重実行を阻止。TTL(既定90日)で自動失効させストレージコストを最適化

  • React 19 + Vite + MUI + TanStack Query(フロント4面): 顧客・加盟店・管理の各UIを、型安全(TypeScript)・APIキャッシュ効率・a11y配慮で構築。社内ダッシュボードはNext.js + Mantineで実装し、全フロントにSentryでエラー可観測性を付与

  • Cognitoカスタム認証 + カードPIN(PBKDF2)+ 最小権限IAM: LINE/メールOTP等のCUSTOM_AUTHチャレンジで多様なサインインを実現し、カードPINはPBKDF2-HMAC(10万回反復・32バイトソルト・定数時間比較)でハッシュ化。IAMロールはスタック単位で最小権限に分離

実施したこと(Action)

  • 【フルスタック横断実装】顧客アプリ(React 19 / Vite / MUI 7 / TanStack Query / AWS Amplify)・加盟店フロント(React 19 / MUI 6)・管理フロント(React 18 / MUI 5)・社内ダッシュボード(Next.js 16 / Mantine 8)の4フロントと、顧客・加盟店・管理・店頭端末の4つのPythonサーバーレスバックエンド(AWS SAM・OpenAPI定義)を実装。カード決済・チャージ(有効期限付き)・電子商取引(カート/注文/配送先)・Jクレジット・地域通貨・ポイント・スタンプラリー・抽選・ランキング・クーポンといった機能群を横断して担当

  • 【冪等性による二重課金防止】クライアント発行の冪等性キーをソートキーcard_op_idem#<操作>#<key>に連結し、attribute_not_existsの条件付き挿入で重複決済を構造的に排除。キーはTTL(既定90日)で自動失効。Stripe WebhookはイベントIDベースの冪等マーカーを同一トランザクション内に含め、ハンドラ失敗時はマーカーも書かれない=Stripeの再送で正しく再処理される設計に

  • 【原子的な残高更新】read-modify-writeを排除し、ADDConditionExpressionで競合下でも残高不整合を防止。残高下限(残高 ≥ 金額)・チャージ上限(残高 + 金額 ≤ 上限)を条件式で表現し、失敗時はトランザクション全体を巻き戻したうえで原因をINSUFFICIENTCAP_EXCEEDEDCONFLICTTHROTTLEDに正確に分類してHTTPステータスへマッピング

  • 【一時的競合のみを吸収する再試行】楽観的並行制御によるTransactionConflictのみを指数バックオフ(基点50ms×2^n)+ジッター(±50%)で最大3回再試行。ConditionalCheckFailed(残高不足・冪等衝突などの意味論的失敗)は再試行せず即時伝播。reasonコード判定は共有モジュールにSSoT化し、競合発生時はCloudWatchメトリクスを発火させてアラームで急増を検知

  • 【ゼロダウンタイム移行(mirror writes)】God Recordの分割を『二重書き込み→読み替え・重複排除→旧データ撤去』に分解し13フェーズ超を無停止で実施。各ビルダーは原子的ADDif_not_existsで冪等に動作し、削除意図はNoneと区別する明示的なCLEARセンチネルで表現。書き込み不要時は空配列を返してno-op化

  • 【金額の精度保証】金額・CO2換算(換算レートはDecimal('0.01'))をDecimalで一貫処理し、floatは型・実行時の双方で拒否。丸め誤差の累積を排除し、境界条件まで含む単体テストで検証

  • 【認証・セキュリティ】Cognitoカスタム認証(サインアップ/確認後フック/CUSTOM_AUTHチャレンジ)を実装し、カードPINはPBKDF2-HMAC(10万回反復・32バイトCSPRNGソルト・定数時間比較)でハッシュ化。入力は境界で検証し、IAMはスタック単位で最小権限に分離。ログはメール・電話をマスクし、PIN・トークン類は一切出力しない

  • 【可観測性・回復性・冪等な非同期処理】CloudWatchアラーム20超+複合アラームと構造化ログ(重大度に応じたSlack通知)で本番を可観測化。DRはAWS Backup+Vault Lock+PITR+専用DRボールトで多層化。従業員カードの月次一括課金・CO2集計はSQS FIFO+デッドレターキューで順序保証・冪等・自動再処理を担保

  • 【CI/CD・型安全・品質ゲート】GitHub Actionsでruffblackmypy --strictdisallow_untyped_defs)/pytestを自動実行し、pre-commitとローカルCIスクリプトで開発機でも同一ゲートを再現。バックエンドはAny型を排除し、回帰をゴールデンベクタテストで防止

プロダクト全体を貫く設計思想は、決済の「正しさ」を運用ルールやレビューの注意深さではなく、コードの構造とプラットフォームの一貫性プリミティブで保証することでした。

横断実装と共有Layerによる一元化(SSoT / DRY): 顧客・加盟店・管理・店頭端末という4つの利用者面に対し、フロント(React / Next.js)からバックエンド(Python / AWS SAM)まで横断的に実装しました。4つのバックエンドが共通で使う残高・冪等性・取引履歴・認証属性のロジックは、共有Lambda Layerに集約してSSoT化。これにより「1箇所直せば全Lambdaに反映」される構造を作り、マルチサーフェスでも不整合を生まない土台を築きました。

冪等性と原子性を1トランザクションに束ねる: 決済処理を、副作用のない純粋なビルダー関数群(冪等マーカー挿入・残高ADD・履歴記録・メトリクス更新)として組み立て、単一のTransactWriteItemsで原子的にコミットします。冪等キーのattribute_not_exists挿入と残高のADDが同じトランザクションに同居するため、リトライ時の二重課金と競合下の残高不整合を、実行時ではなく設計時点で同時に排除しています。結果として、本番稼働中の二重課金・残高不整合の発生は0件を維持しています。

一時的競合と意味論的失敗を峻別する: 再試行してよいのは楽観ロックによる一時的なTransactionConflictだけです。残高不足やチャージ上限超過、冪等衝突といったConditionalCheckFailedは『再試行しても結果が変わらない』ため即座に伝播し、原因を型付きのenumに分類してAPIエラーへ落とし込みます。

走りながらエンジンを乗せ換える(ゼロダウンタイム移行): 残高モデルの分割を、二重書き込み→読み替え(重複排除)→旧データ撤去という冪等なフェーズに分解しました。各ビルダーが原子的ADDif_not_existsで構成されるため、バックフィルの再実行や部分的な失敗が起きても最終状態は一意に収束します。これにより、稼働中の決済を1秒も止めずに13フェーズ超のスキーマ進化を完遂しました。

本番運用品質を機能と同列で作り込む: 可観測性(CloudWatchアラーム・Slack通知・構造化ログ)、回復性(AWS Backup・Vault Lock・PITRによる多層DR)、セキュリティ(Cognito認証・PBKDF2のPINハッシュ・最小権限IAM・PIIマスキング)、冪等な非同期処理(SQS FIFO+DLQ)を、CI/CD(GitHub Actions・mypy strict・pre-commit)で守りながら実装。これらを通じて、フロントのUX/a11yから決済の正確性、本番運用の可観測性・回復性まで一貫して責任を持ちました。

技術選定の理由

  • 共有Lambda LayerにSSoT集約:4バックエンドの中核ロジックを一元化(DRY)

  • 冪等性キー+attribute_not_exists+TTL:リトライ時の二重課金を構造的に防止

  • atomic ADD+ConditionExpression+トランザクション:競合下の残高整合性

  • TransactionConflictのみ再試行・意味論的失敗は即時伝播:無駄なリトライを排除

  • 純粋関数ビルダー+mypy strict:DBなしでテスト可能・Any型を排除

  • AWS SAM / CloudWatch / AWS Backup:可観測性・回復性をコードで担保

担当領域

  • 顧客・加盟店・管理フロント(React / MUI / TanStack Query)と社内ダッシュボード(Next.js / Mantine)の実装
  • 顧客・加盟店・管理・店頭端末の4つのPythonサーバーレスバックエンド(AWS SAM / OpenAPI)の実装
  • 決済信頼性レイヤー(冪等性・原子的残高更新・mirror writesゼロダウンタイム移行)の設計・主導
  • 共有Lambda LayerへのSSoT集約と決済ロジックの純粋関数化
  • 認証(Cognitoカスタム認証)・カードPIN(PBKDF2)・最小権限IAM・入力検証
  • 可観測性(CloudWatchアラーム・Slack通知・構造化ログ)とDR(AWS Backup・Vault Lock・PITR)
  • 従業員カード月次課金・CO2集計の冪等な非同期処理(SQS FIFO+DLQ)
  • CI/CD(GitHub Actions・mypy strict・ruff・pytest・pre-commit)と単体テスト(境界条件・ゴールデンベクタ)

使用技術

Python
TypeScript
React
Next.js
Vite
Material-UI
Mantine
TanStack Query
AWS Amplify
Sentry
AWS Lambda
AWS Lambda Layers
DynamoDB
DynamoDB Transactions
Amazon Cognito
API Gateway
Amazon SQS
Amazon SNS
Amazon SES
Amazon S3
AWS Backup
Amazon CloudWatch
AWS SAM
CloudFormation
Stripe
boto3
OpenAPI
mypy
ruff
pytest
GitHub Actions

数字で見る成果

本番での二重課金
0件冪等性+原子的トランザクションで構造的に排除(本番稼働中)
ゼロダウンタイム移行
13フェーズ+無停止での段階的スキーマ進化
担当コミット
403件リポジトリ全コミットの約6割(694中403)を担当する中核エンジニア
横断実装したアプリ
8アプリ4バックエンド+4フロントを横断して実装

成果

  • 【最大の成果】本番稼働中、二重課金・残高不整合の発生 0件を維持(冪等性+原子的トランザクションで構造的に排除)
  • 顧客・加盟店・管理・店頭端末の4面(バックエンド/フロント)と共有基盤を横断実装し、リポジトリ全コミットの約6割(694中403)を担当する中核エンジニアとして開発を牽引
  • 冪等性キー+attribute_not_exists条件で、リトライ時の二重課金を構造的に防止(顧客決済・Stripe Webhookの双方)
  • atomic ADD+条件式+トランザクションにより、競合下でもマイナス残高・残高不整合を起こさない決済処理を実現
  • TransactionConflictのみを指数バックオフ+ジッターで最大3回再試行し、意味論的失敗は即時伝播することで、一時的競合を吸収しつつ無駄なリトライを排除
  • 二重書き込みによる段階移行で、稼働中サービスを1秒も止めずに決済データモデルを進化(13フェーズ超)
  • 金額・CO2換算をDecimalで一貫処理し、丸め誤差の累積を排除
  • カードPINをPBKDF2-HMAC(10万回反復・32バイトソルト・定数時間比較)でハッシュ化し、PII・秘匿情報をログから排除
  • CloudWatchアラーム20超+構造化ログ(Slack通知)と多層DR(AWS Backup・Vault Lock・PITR)で本番運用品質を担保
  • 従業員カード月次課金・CO2集計をSQS FIFO+DLQで冪等・順序保証・自動再処理化
  • 決済ロジックを純粋関数化しDBなしの単体テスト+ゴールデンベクタで固定。GitHub Actions+mypy strictでany型を排除し回帰を防止

同様の課題、抱えていませんか?

あなたのビジネス課題も、最新の技術で解決できます。 まずは30分の無料技術相談から、状況をお聞かせください。

自社の課題もSaaS化できるか相談する

プロジェクト単位(請負)・技術顧問、どちらにも対応可能です

全ケーススタディを見る