第26章:境界と一貫性(トランザクションの気持ち)🔁
この章のゴール🎯✨
- **「どこまでを“同時に正しい”にするか」**を決められるようになる✅
- 境界(BC)をまたぐと起きる “ズレ” を、仕様として説明できるようになる🗣️
- C#で「境界を越える更新」を安全に扱う定番パターン(Saga / Outbox)を、雰囲気だけでもつかむ💻🛡️ (Microsoft Learn)
1) まずは超ざっくり:一貫性ってなに?✅

一貫性(Consistency)は、ざっくり言うとこう👇
-
**「今この瞬間、正しいと言える状態」**が守れてること✨
-
たとえばミニECなら…
- 在庫が -1個 になってない?😱
- 支払いが失敗したのに「注文確定」になってない?💥
- 同じ注文が二重に確定してない?😵💫
ここで大事なキーワードが “同時に正しい範囲” だよ🧠✨ その「範囲」を決めるのが、境界(BC)の超重要なお仕事💪
2) トランザクションの“気持ち”ってこういうこと🥺🔁
トランザクションは、気持ち的にこう👇
「いまやってる一連の処理、全部成功ならOK。 1つでも失敗なら、全部なかったことにしてほしい🥹」
つまり “全部 or ゼロ”(Atomicity)だね✅ そしてこれがやりやすいのは…
- 同じアプリ
- 同じデータベース
- 同じ境界(BC)の中
この条件がそろうと、ACID(堅い一貫性)を守りやすいよ💎 (Microsoft Learn)
3) じゃあ、境界(BC)をまたぐと何が起きるの?🧱➡️🧱
BCをまたぐと、だいたいこうなる👇
- AのBCで保存✅
- 次にBのBCで保存しようとしたら…失敗❌
- でもAはもう保存しちゃった😇(戻せない or 戻すのが地獄)
これが 「分散トランザクション地獄」 の入口🕳️💀
「TransactionScopeでまとめればよくない?」って思うよね🤔
たしかに .NET には TransactionScope みたいに “広い範囲を1トランザクション扱い” にする仕組みがあるよ💡 (Microsoft Learn)
でも注意点が強い⚠️
- 分散トランザクション(MSDTCにエスカレーション)が絡むと、環境や構成で 普通に詰む ことがある💥
- 少なくとも System.Transactions の分散トランザクションは Windows の .NET 7 以降で追加、それ以外(古い .NET や非Windows)では失敗する、と明記されてるよ📌 (Microsoft Learn)
- しかも「BCは独立性が命」なのに、分散トランザクションは 実行時に密結合 になりがち😵💫
あと、暗黙の分散トランザクションを許す設定として TransactionManager.ImplicitDistributedTransactions がある(= 勝手に昇格するのを許可する)っていう情報もあるよ⚙️ (Microsoft Learn)
でも教材的には結論これ👇
✅ 「境界を越えるのに“1発で全部確定”を狙いすぎない」 → 代わりに、設計パターンで安全にする💪✨
4) 境界と一貫性の決め方:3つの質問📝💡
境界を切ったあと、次の3つを決めるとスッキリするよ✨
Q1. 「絶対に同時に正しくないとダメ」なのはどれ?🧷
- 例:注文確定したら「注文番号」と「明細合計」は必ず一致してないと困る → これは 同じBC内 に置いたほうが楽✅
Q2. 「ちょいズレてもOK(あとで追いつけばOK)」はどれ?🕒
- 例:注文確定の直後、配送側の画面に反映が 数秒遅れる → ユーザー体験的に許されるなら OK😊
Q3. 「ズレたとき、どうやって戻す?」🔙
- 例:在庫の確保に失敗したら「注文をキャンセル扱い」にする → これが 補償(Compensation) の考え方だよ🧯 (Microsoft Learn)
5) 境界をまたぐ“正しさ”の作り方:定番3パターン🧰✨
パターンA:そもそも同じ境界に入れる(強一貫性が必要)🧲
- 「同時に正しくないと本当に困る」なら 👉 境界を分けない ほうが正解のときがある🙆♀️
パターンB:Saga(分割して、失敗したら“取り消し”する)🧩🧯
-
ざっくり:
- 各サービス(BC)で ローカルトランザクション を確定✅
- 次のBCへイベント/メッセージでつなぐ📨
- 途中で失敗したら、補償処理で巻き戻す🔙
-
これがSagaだよ🗺️ (Microsoft Learn)
パターンC:Outbox(DB保存とイベント発行の“二重書き込み”を潰す)📤📦
-
ありがちな事故:
- DB保存は成功✅
- でもイベント送信に失敗❌(ネットワークとか)
- 他BCが知らないまま😇
-
Outboxはこれを避けるために **「DBの同じトランザクションで outbox に“送る予定”も保存」**して、 あとで確実に送るやつ📮✨ (Microsoft Learn)
6) ミニECで体験!「許せるズレ」を決める例🛒✨
例テーマ:注文確定で “在庫確保” もしたい📦
やりたいこと:
- 注文を作る(注文BC)🧾
- 在庫を引く/確保する(在庫BC)📦
ここで質問👀 「注文確定ボタンを押した瞬間に、在庫も絶対確保できてないとダメ?」
- YESなら: 👉 同一BCに寄せる / 同期APIで確保を確認する(ただし失敗時UXも設計)
- NOなら: 👉 注文は「受付」状態で確定 → 在庫確保できたら「確定」へ✨ 👉 途中失敗なら「在庫不足でキャンセル」へ😢 👉 これが Sagaっぽい発想🧩
7) C#で “境界内” をちゃんと固める(超ミニ例)🧱💻
まずは BC内は堅く が基本だよ✅
EF Coreは SaveChanges が暗黙トランザクションになったり、明示トランザクションもできるよ📌 (Microsoft Learn)
// 例:注文BCの中で「注文作成 + 監査ログ」を同時に確定したい
await using var tx = await db.Database.BeginTransactionAsync();
try
{
var order = Order.Create(customerId, items); // ここでBC内ルールを守る(合計金額など)
db.Orders.Add(order);
db.AuditLogs.Add(new AuditLog("OrderCreated", order.Id));
await db.SaveChangesAsync();
await tx.CommitAsync();
}
catch
{
// どれか失敗したら「なかったこと」にする
//(Commitされてないのでロールバックされる)
throw;
}
ポイント✨
- **BC内の不変条件(守りたいルール)**は、ここで守る💪
- BC内をグラつかせたまま、BC間の整合を頑張るのはキツい😵💫
8) C#で “境界をまたぐ” を安全にする:Outboxの超ミニ例📤🧾
Outboxは「注文保存」と「イベント発行予定」を 同じトランザクション に入れるのがコツ✅ (Microsoft Learn)
public class OutboxMessage
{
public Guid Id { get; set; }
public DateTimeOffset OccurredAt { get; set; }
public string Type { get; set; } = "";
public string PayloadJson { get; set; } = "";
public DateTimeOffset? PublishedAt { get; set; }
}
public async Task PlaceOrderAsync(...)
{
await using var tx = await db.Database.BeginTransactionAsync();
var order = Order.Create(...);
db.Orders.Add(order);
// 「送る予定」もDBに保存(これがOutbox)
db.OutboxMessages.Add(new OutboxMessage {
Id = Guid.NewGuid(),
OccurredAt = DateTimeOffset.UtcNow,
Type = "OrderPlaced",
PayloadJson = JsonSerializer.Serialize(new { orderId = order.Id })
});
await db.SaveChangesAsync();
await tx.CommitAsync();
}
// 別のバックグラウンド処理で Outbox を読んで Publish する(失敗したらリトライ)
これで何が嬉しい?😊
- DB保存だけ成功して「イベント送れなかった…」が減る✅
- 送信側の失敗は リトライで回復しやすい🔁
- 受信側は 同じイベントが2回来ても平気(冪等性) を作ると最強💪✨
9) Sagaを“気持ちで理解”するミニ図🗺️✨
注文BC: 注文受付(受付中) ✅ → イベント OrderPlaced 📣
│
▼
在庫BC: 在庫確保 ✅ → イベント StockReserved 📣
│
▼
請求BC: 支払い確定 ✅ → イベント PaymentCaptured 📣
│
▼
注文BC: 注文確定(確定) 🎉
もし途中で在庫確保が失敗したら…👇
- 注文BCに「在庫不足」イベントが戻ってくる
- 注文を「キャンセル」にする(補償)😢🧯
この考え方が、MicrosoftのSaga説明そのものだよ📌 (Microsoft Learn)
10) “許せるズレ”を決めるチェックリスト✅📝
境界を越えるたびに、これだけ確認すると事故が減るよ✨
- いま絶対に守りたいルールは何?(不変条件)🧷
- ユーザーに見せる状態はどうする?(受付中/処理中/確定/失敗)🖥️
- 失敗したときの戻し方は?(補償・キャンセル・返金)🔙
- 二重実行されても平気?(冪等性)🔁
- 監査ログ/履歴は残る?(あとで追える)🕵️♀️
11) よくあるつまずきポイント😵💫⚠️
- **「境界を分けたのに、全部同期で固めようとして結局密結合」**になる💥
- “最終的に一致する”の定義がなくて、データが信用できなくなる😇
- イベントが重複して、在庫が二重に引かれる(冪等性不足)😱
- 「失敗=例外」しか用意してなくて、UXが崩壊する😢
12) お助けAIプロンプト🤖✨
- 「このユースケースで**“同時に正しい”が必要なルール**を3つ挙げて」✅
- 「境界をまたぐ操作を、Sagaで成功/失敗/補償の流れにして」🧩
- 「Outboxを入れるなら、保存するPayloadの最小フィールドを提案して」📤
- 「二重実行に備えた冪等性のキー設計を案出しして」🔁
13) 2026のC#メモ(最新系)📌✨
C#は C# 14 が最新で、.NET 10 上で使えるよ(Visual Studio 2026でも試せる)🧡 (Microsoft Learn)