Amazon DynamoDB は、ミリ秒単位のレスポンスタイムと自動スケーリングを提供する AWS のフルマネージド NoSQL データベースです。マイクロサービスや高トラフィックなウェブアプリケーションのバックエンドとして広く採用されています。一方で、複雑なビジネスロジックを実装する際に「ネストされたトランザクション」が使いたいというニーズが開発現場から多く聞かれます。しかし、DynamoDB の標準 API はネストされたトランザクションを直接サポートしていません。本記事では、AWS が公開した C# 向けのオープンソースフレームワーク「amazon-dynamodb-transaction-framework」を使って、この課題をどのように解決できるかを解説します。
Amazon DynamoDB のトランザクション機能とその制約
DynamoDB のトランザクション機能は、複数のアイテムに対する「すべて成功するか、すべて失敗するか」のアトミックな操作を実現します。これにより、ACID(原子性・一貫性・分離性・永続性)の特性を持つ操作が可能になります。
主要な API は以下の 2 つです。
- TransactWriteItems: Put / Update / Delete / ConditionCheck を最大 100 件グループ化して一括実行します。合計サイズの上限は 4 MB です。
- TransactGetItems: Get 操作を最大 100 件グループ化して読み取りトランザクションとして実行します。こちらも最大 4 MB です。
トランザクション利用時のコスト面では注意が必要です。通常の書き込み・読み込みと比較して、WCU(書き込みキャパシティユニット)と RCU(読み込みキャパシティユニット)がそれぞれ 2 倍消費されます。これは DynamoDB が各アイテムに対して「準備」と「コミット」の 2 段階の内部処理を行うためです。
しかし最大の制約は、同一アイテムへの複数操作が禁止されていること、そしてネストされたトランザクションが標準ではサポートされていない点です。複数のビジネスロジックレイヤーでトランザクションを扱う場合、この制限が実装の妨げになることがあります。
ネストされたトランザクションが必要になる理由
シンプルな CRUD 操作では標準の DynamoDB トランザクションで十分ですが、エンタープライズアプリケーションでは状況が変わります。たとえば、以下のようなシナリオを考えてみましょう。
- 注文処理サービスが在庫サービスと決済サービスをオーケストレーションする場合
- 各サービスが独自のトランザクション境界を持ちながら、上位の親トランザクションで全体の整合性を保証したい場合
- 一部の子処理が失敗したときのみ部分ロールバックを実行したい場合
このようなシナリオでは、単一のフラットなトランザクションではなく、「階層的なトランザクション管理」が求められます。また、各サービスレイヤーがトランザクションの詳細を知らずに済むよう、コードの再利用性とモジュール性を高めることも重要です。
従来は、こうした要件を満たすためにアプリケーション側で複雑な状態管理ロジックを実装する必要がありました。しかし、AWS が提供するフレームワークを活用することで、このような複雑さを抽象化できます。
フレームワークのアーキテクチャと主要コンポーネント

「amazon-dynamodb-transaction-framework」は、DynamoDB の標準トランザクション API の上に抽象レイヤーを提供する C# ライブラリです。GitHub の aws-samples 組織から MIT-0 ライセンスで公開されており、商用利用も含めて自由に使用できます。
フレームワークの主要コンポーネントは以下の 3 つです。
TransactScope
トランザクションのライフサイクル全体(開始・書き込みアイテム追加・コミット・ロールバック)を管理する中心的なクラスです。内部ではスタック構造でネストを管理しており、次のフィールドを保持しています。
_client: DynamoDB クライアント_transactRequest: トランザクション要求オブジェクト_subTransactScope: ネストされた子スコープへの参照_parentTransactScope: 親スコープへの参照
TransactExtensions
DynamoDB のアイテム操作をトランザクションアイテムに変換するユーティリティクラスです。非トランザクション操作とトランザクション操作の間でコードを重複して書く必要がなくなります。単一責任の原則に従い、変換ロジックをこのクラスに集約することで、各ビジネスロジックのコードがすっきりします。
AttributeExtensions
エンティティオブジェクトを DynamoDB の AttributeValue 形式に変換するユーティリティです。C# のオブジェクトから DynamoDB が要求する低レベル表現への変換処理をカプセル化します。
プロジェクト構成は、データアクセス層(BootCamp.DataAccess)、ビジネスロジック層(BootCamp.Service)、メインアプリケーション(BootCampDynamoDB)の 3 層に分かれており、各層が独立してトランザクションを扱える設計になっています。
TransactScope を使ったネストされたトランザクションの実装

TransactScope の主要なメソッドと動作を見ていきましょう。
Begin() メソッド
Begin() を初めて呼び出すと、新しい TransactWriteItemsRequest が初期化されます。同じスコープで再度呼び出した場合は、子スコープが作成され、その子スコープが親スコープへの参照を保持します。これによりスタック構造が形成され、ネストの深さに応じた階層管理が実現されます。
// 外側(親)トランザクションの開始
var outerScope = new TransactScope(client);
await outerScope.Begin();
// 内側(子)トランザクションの開始
await outerScope.Begin(); // 内部で _subTransactScope が作成される
AddTransactWriteItem() メソッド
トランザクションに書き込みアイテムを追加します。null チェックが実装されており、不正なアイテムが混入するのを防ぎます。現在アクティブなスコープ(最も深い子スコープ)に対してアイテムが追加されます。
Commit() メソッド(LIFO 順)
コミット操作は Last-In-First-Out(後入れ先出し)の順序で実行されます。つまり、最も深い子スコープが最初にコミットされ、その後に親スコープがコミットされます。すべての処理は非同期で行われ、ConfigureAwait(false) を使用してデッドロックを防いでいます。
// コミットは子から親の順(LIFO)
await outerScope.Commit();
// → 内部で子スコープを先にコミット → 親をコミット
Rollback() メソッド(再帰的)
ロールバックも再帰的に実行されます。子スコープがロールバックされた後、親スコープのトランザクション要求が null で初期化されて状態がクリアされます。これにより、失敗した場合でも完全なクリーンアップが保証されます。
try
{
await outerScope.Begin();
// ... 処理 ...
await outerScope.Commit();
}
catch (TransactionCanceledException ex)
{
await outerScope.Rollback(); // 子から再帰的にロールバック
throw;
}
エラー処理とベストプラクティス
DynamoDB トランザクションを本番環境で使用する際は、適切なエラー処理が不可欠です。主要な例外クラスとその対処法を以下に示します。
- TransactionCanceledException: トランザクションがキャンセルされた場合に発生します。条件式の不成立、同一アイテムへの重複操作、キャパシティ不足などが原因として挙げられます。AWS SDK は自動再試行しないため、アプリケーション側での再試行ロジックが必要です。
- TransactionConflictException: 同一アイテムへの同時アクセスが発生した場合に発生します。指数バックオフを使った再試行が有効です。
- IdempotentParameterMismatchException: べき等性確保のためのクライアントトークンを使い回した場合に発生します。トークンの有効期限は 10 分です。
運用上のベストプラクティスとして、以下の点を押さえておきましょう。
- 自動スケーリングを有効化する: トランザクションは通常の 2 倍の WCU/RCU を消費するため、キャパシティ不足に陥りやすくなります。オンデマンドモードまたは自動スケーリングの活用が推奨されます。
- 不要なトランザクションを避ける: 単一アイテムの更新など、トランザクションが不要なケースでは通常の操作を使用することでコストを抑えられます。
- 同一アイテムへの同時更新を最小化する: コンフリクトの原因となるため、データモデルを設計する段階でアクセスパターンを考慮することが重要です。
- グローバルテーブルではリージョン間トランザクション不可: グローバルテーブルを使用している場合、トランザクションは同一リージョン内でのみ有効です。
- 大量データ取り込みには BatchWriteItem を使用する: トランザクションの制約(100 アイテム・4 MB)の外で大量データを扱う場合は、BatchWriteItem のほうが適しています。
まとめ
本記事では、Amazon DynamoDB でネストされたトランザクションを実現するための C# フレームワーク「amazon-dynamodb-transaction-framework」について解説しました。
DynamoDB の標準 API が提供する TransactWriteItems / TransactGetItems は強力な機能ですが、複数のサービスレイヤーにまたがる複雑なトランザクション管理には限界があります。このフレームワークが提供する TransactScope を活用することで、以下のメリットが得られます。
- LIFO 順のコミットと再帰的ロールバックによる安全なトランザクション管理
- 単一責任の原則に従ったモジュール化された実装
- 各レイヤーが DynamoDB API の詳細を意識せずにトランザクションを扱える抽象化
- 部分ロールバックを含む柔軟なエラー処理
MIT-0 ライセンスで公開されており、商用利用も含めて自由に導入できます。DynamoDB を活用した C# アプリケーションで複雑なトランザクション要件に直面しているチームは、ぜひ検討してみてください。ソースコードは GitHub の aws-samples/amazon-dynamodb-transaction-framework から参照できます。
















