May 24, 2026
集約・トランザクション・整合性 — Q&Aサービスを題材にしたドメインモデリング設計メモ
知恵袋ライクなQ&Aサービスを題材に、バリデーションの置き場所、集約とトランザクションの関係、結果整合性のコスト、複数集約をまたぐ判断フローをまとめた設計メモ。
1. バリデーションの置き場所(3層)
性質で分類すると、置き場所が自然に決まる。
| 種類 | 内容 | 置き場所 |
|---|---|---|
| 形式的バリデーション | メール形式・文字数・必須 | 外側(ファクトリ / DTO) |
| ドメイン不変条件 | そのモデルが常に守るべきルール | モデル(集約) |
| 文脈依存ルール | 権限・レート制限・他集約依存 | ユースケース |
判断の問い
- このルールはモデル単体の情報だけで判断できる? → YESならモデル
- このルールが破れたモデルは存在しちゃダメ? → ダメならコンストラクタ/メソッドで強制
原則:ドメイン不変条件はモデルに閉じ込め、「壊れたモデルは生成できない」状態にする(フィールド非公開+ファクトリ経由生成)。
アンチパターン
- 貧血ドメインモデル:不変条件まで全部ユースケースに書き、モデルがただのデータ入れ物になる
- 文脈の持ちすぎ:権限やレート制限をモデルに押し込み、純粋さが壊れる
2. 集約 vs トランザクション
| 集約 | トランザクション | |
|---|---|---|
| レイヤー | ドメイン(論理) | インフラ(技術) |
| 目的 | 整合性の境界を定義 | 原子性を保証 |
| 決め方 | モデリングで先に決める | 集約に合わせて実装 |
| またぎ | 1集約に収める | 技術上はまたげるが避ける |
標語:集約は「ここは一瞬たりとも矛盾させない」という宣言。トランザクションはそれを守る道具。だから 1集約 ≒ 1トランザクション になるが、集約のほうが先。
- 集約は小さく切るほどよい(ロック競合が減りスケールする)
- トランザクション単位を先に決めると巨大集約(God Aggregate)になりがち
3. 結果整合性のコスト
「分ければ疎結合」と綺麗な顔で来るが、強整合ならDBがやってくれた面倒事を全部自前で引き受ける。
- 二重書き込み問題:DB更新とイベント発行の間で落ちると不整合 → Transactional Outbox で同一Txに収める
- 冪等性:配信は at-least-once 前提。同じイベントが2回来ても壊れないように受信側を作る
- 順序保証なし:イベントは発行順に届かない。バージョン/シーケンスで弾く
- 可観測性:「今どこまで伝播したか」が見えない瞬間が常にある。トレーシングが要る
- 補償処理(Saga):ロールバックの代わりに打ち消し処理を自前で書く
→ 結果整合性はタダじゃない。安易に集約を分けすぎると無駄に地獄を背負う。
4. 複数集約をまたぎたくなったときの対処
まず原因を切り分ける
- 原因A:集約の切り方を間違えてる → 集約を引き直して統合(そもそもまたがない)
- 原因C:更新でなく読み取りでまたぎたいだけ → CQRSの読みモデルで解決(Tx境界と無関係)
- 原因B:本当に別集約で整合性が要る → 下の対処へ(本題)
判断フロー(上ほど安い/できるだけ上で止める)
- いつも一緒に変わって一瞬もズレちゃダメ? → 集約を統合
- 更新じゃなく表示のためのまたぎ? → CQRSの読みモデル
- 頻度低い & 同一DB & 競合しない? → 割り切って同一Txで複数集約を更新
- 疎結合・スケールが本当に必要? → Outboxで結果整合
- 複数サービスにまたがる長い業務フロー? → Saga
知恵袋での当てはめ
| ケース | 判定 | 対処 |
|---|---|---|
| Questionの状態 と bestAnswerID | 同じ集約 | またがない(最初から統合) |
| ベストアンサー選定 → 回答者スコア+10 | 別集約・ズレ許容 | まず同一Tx割り切り、競合し始めたらOutbox |
| 質問一覧に回答者名・回答数を表示 | 読み取りのまたぎ | CQRSの読みモデル |
5. 全体を貫く指針
- またがない設計を目指す:迷ったら集約の引き直しとCQRSで「またぐ必要」を消せないか疑う
- 本物のまたぎは、いきなり結果整合に飛ばない:第一候補は「同一DB・同一Txで割り切る」
- 結果整合・Sagaは、スケール/疎結合の要求が実証されてから昇格させる
- 原理を守ろうと最初から重武装するのが、実は一番事故る
段階的アプローチ
- モジュラモノリス(集約は論理的に分け、物理は同一DB・同一Tx)
- 同期的ドメインイベント(プロセス内、同一Tx内ハンドラ)
- 本当に必要な箇所だけ Outbox + メッセージキューに昇格
補足:ステートマシンによるモデリング
状態遷移に明確な制約がある集約(知恵袋ならQuestion)は、sealed interface 風の型でステートマシン表現するとうまみが出る。
- 効く条件:状態が3つ以上 × 遷移に明確なルール × 不正遷移が事故になる
- Questionの例:受付中 → 解決済み / クローズ。「受付中のときだけベストアンサーを選べる」等を型で表現
- 回答やユーザーなど状態が少なく遷移が緩いものは enum/bool で十分(やりすぎ注意)