第09章:不変条件(Invariants)超入門🧷🛡️
この章でできるようになること🎯✨
- 「絶対に壊しちゃダメなルール(不変条件)」を見つけられる👀🧠
- 不変条件を“あちこちに散らさず”に守れるようになる📌✅
- 不変条件をテスト観点(Given-When-Then)に変換できる🧪🔁
9-1. 不変条件(Invariant)ってなに?🧷🤔

不変条件は、超ざっくり言うと…
「この世界では、これだけは絶対に起きちゃダメ!」 「いつでも必ず守られてないと困る!」
っていう “ビジネス上のガードレール” のことだよ🚧✨
たとえば(ショッピングカート🛒の例)
- ✅ チェックアウト済みのカートに商品追加はできない
- ✅ カートが空ならチェックアウトできない
- ✅ 数量は 1 以上(0 やマイナスはダメ)
こういうのが不変条件候補だよ🧷🛡️
9-2. なんでイベントソーシングだと超大事?📜🔥
イベントソーシングは「状態を保存」じゃなくて「出来事(イベント)を積む」設計だったよね🔁✨ そしてイベントは 基本的に追加専用(append-only) で、履歴として残り続ける📚🧾 だから一度「おかしなイベント」を出しちゃうと、ずっと残っちゃう可能性が高い😱💦
イベントソーシング自体の考え方(イベントを追加専用ストアに記録する)は、Microsoftのパターン解説でもこう整理されているよ📘✨ (Microsoft Learn)
👉 つまり… 「イベントを出す前に、不変条件で止める」 が超重要になるの!🛑🧷
9-3. 不変条件と“ただの入力チェック”の違い🧠🧁
ここ、よく混ざるので分けようね😊✨
✅ 入力チェック(Validation)
- 例:メールアドレス形式が変、必須項目が空、文字数が長すぎる
- UIやAPI層でもやる(ユーザーに優しい)📮💕
✅ 不変条件(Invariant)
- 例:チェックアウト済みカートに商品追加は絶対ダメ
- これは “ドメイン(中核ルール)側で絶対守る” 🧷🛡️
- UIをすり抜けても、最終防衛線で止める🚧✨
9-4. 不変条件はどこに置くのが正解?📍🧩
結論:ドメインの中心(のちの章で出てくる Aggregate の中) に集めるのが基本だよ📌✨ (Aggregate は10章で詳しくやるけど、今は「1つのルールのまとまり」くらいでOK😊)
イベントソーシング界隈でも、集約(Aggregate)が整合性の境界になって不変条件を守る という整理がよく使われるよ🧠🛡️ (Event Sourcing Guide)
9-5. 不変条件の“見つけ方”テンプレ🕵️♀️✨
次の質問に「はい」が多いほど、不変条件っぽいよ✅
不変条件チェックリスト✅🧷
- 「それが破られると、ビジネス的に事故る?」💥
- 「一瞬でもその状態が存在したら困る?」⛔
- 「後から直すじゃなく、その場で止めたい?」🛑
- 「1つのまとまりの中だけで判断できる?」(他の集約を見ないと判断できないなら境界の相談ポイント👀)
9-6. 例で理解しよう🛒✨(ショッピングカート)
カートの不変条件(例)🧷
- チェックアウト済みなら変更禁止 🔒
- 数量は1以上 ➕
- 空カートはチェックアウト不可 🕳️❌
ここで大事なのは、イベントソーシングでは
- コマンドを受け取る(例:AddItem)📮
- いまのイベント列から状態を復元する(Rehydrate)🔁
- 不変条件をチェックする🧷
- OKなら、新しいイベントを生成して保存する📜✅
という流れになりがち、ってこと✨ (この「型」は16章でガッツリ固めるよ😊)
9-7. C#で“不変条件ガード”を書いてみよう✍️🧷
ここでは最小形で、雰囲気をつかもう😊✨ (まだEventStoreとかは作らないよ。不変条件だけに集中!🧠)
① 状態(State)っぽいもの
public sealed class CartState
{
public bool IsCheckedOut { get; private set; }
public Dictionary<string, int> Items { get; } = new();
public void Apply(object @event)
{
switch (@event)
{
case ItemAdded e:
Items[e.Sku] = Items.TryGetValue(e.Sku, out var qty) ? qty + e.Quantity : e.Quantity;
break;
case CheckedOut:
IsCheckedOut = true;
break;
}
}
}
public sealed record ItemAdded(string Sku, int Quantity);
public sealed record CheckedOut;
ポイント😊
Applyは「イベントが起きた結果、状態がどう変わるか」だけを書く✍️- イベントが正しい前提でスッキリ書く(正しさは“決める側”で守る)🧷✨
② 不変条件ガード(超シンプル版)
public static class Invariants
{
public static void EnsureNotCheckedOut(CartState state)
{
if (state.IsCheckedOut)
throw new InvalidOperationException("チェックアウト済みのカートは変更できません。");
}
public static void EnsureQuantityPositive(int quantity)
{
if (quantity <= 0)
throw new InvalidOperationException("数量は1以上である必要があります。");
}
public static void EnsureNotEmptyOnCheckout(CartState state)
{
if (state.Items.Count == 0)
throw new InvalidOperationException("空のカートはチェックアウトできません。");
}
}
ここでは例外にしたけど、あとでResult型っぽく安全にする(21章あたり)予定があるから、今は「ガードがある形」を覚えればOKだよ😊🧠
③ コマンド処理っぽい入口で守る(雰囲気)
public static class CartUseCases
{
public static ItemAdded AddItem(CartState state, string sku, int quantity)
{
Invariants.EnsureNotCheckedOut(state);
Invariants.EnsureQuantityPositive(quantity);
// OKなら「出来事」を返す(保存はまだやらない)
return new ItemAdded(sku, quantity);
}
public static CheckedOut Checkout(CartState state)
{
Invariants.EnsureNotCheckedOut(state);
Invariants.EnsureNotEmptyOnCheckout(state);
return new CheckedOut();
}
}
この形ができると、イベントソーシングの一番の怖さ(=変なイベントを出す)を最初に潰せる🧷🛡️✨
9-8. ミニ演習📝✨(不変条件を3つ書いてみよう)
題材はあなたの教材ドメイン(カートでもToDoでも家計簿でもOK)でやってね😊💕
ステップ1:まず自然言語で3つ✍️
- 「〜のとき、〜してはいけない」
- 「〜は常に〜である」
- 「〜が成り立たないなら、操作を拒否する」
例(家計簿💰)
- ✅ 残高が足りないなら出金できない
- ✅ 未来の日付の出金は登録できない(ルールなら)
- ✅ 同じ取引IDは二重登録できない(冪等性にも関係🔁)
ステップ2:それを“if文”に落とす🔽
- 条件(if)
- エラーメッセージ(ユーザーに優しく)💬✨
ステップ3:置き場所を決める📌
- 「画面だけ」❌
- 「DB制約だけ」❌
- “ドメイン側”に集める ✅🧷
9-9. 不変条件 → テスト観点への変換🧪✨
不変条件は、そのまま 失敗テスト になりやすいよ😊🧠
例:空カートはチェックアウトできない🕳️❌
- Given:アイテム0件の状態
- When:Checkoutコマンド
- Then:エラーになる(もしくはイベントが出ない)
この形は20章で本格的にやるけど、今のうちに “変換できる感覚” を持っておくと強い💪🧪
9-10. よくある落とし穴😵💫⚠️
❶ 不変条件が散らばる🌀
UI・API・サービス・DB…あちこちに同じルールがあると、変更で地獄😇 👉 中心に集める📌✨
❷ 「ルール」じゃなく「手順」を不変条件にしちゃう
- 例:「チェックアウトは夜だけ」みたいな運用ルール 👉 それが“絶対に守るべき本質”かは一回疑ってOK😊
❸ 他の集約が必要なルールで詰む
「AとBの合計が〜」みたいなやつ 👉 境界(10章)や、後のSaga/最終的整合性の話に繋がる👀✨
9-11. AI活用🤖✨(不変条件を増やして、品質を上げる)
① 不変条件候補を“増やす”プロンプト💡
- 「題材はショッピングカート。ユーザーが事故りそうな不変条件を10個、初心者向けに」
- 「それぞれ、破られたときに何が困るかも1行で」
② 不変条件を“テスト観点”に変換するプロンプト🧪
- 「この不変条件をGiven-When-Thenに変換して。成功/失敗を1本ずつ」
③ メッセージ改善プロンプト💬
- 「このエラーメッセージ、ユーザーに優しく、でも短くして。3案」
9-12. まとめ🌸✨
- 不変条件は「絶対に壊しちゃダメなルール」🧷🛡️
- イベントソーシングはイベントが履歴として残るから、変なイベントを出さない設計が超重要📜🔥 (Microsoft Learn)
- 不変条件は“中心に集めて”守るのが基本📌✨(集約が整合性境界になって不変条件を守る整理がよく使われるよ)🧠🛡️ (Event Sourcing Guide)
- 不変条件はテスト観点に変換しやすいから、あとで爆伸びする🧪🚀