ドメイン視点の地図 ─ Aggregate / Bounded Context / Repository
ドメイン駆動が “DB は詳細” と言うとき、それは「永続化を考えなくていい」という意味ではない。「ドメインモデルが永続化技術に従属しない」という意味だ。だがこの “従属しない” を実現するためには、ドメイン側に明確な構造がいる。それが Aggregate / Bounded Context / Repository という三つの概念。
この章では、ドメイン側の地図を立てる。
Bounded Context ─ 言語と意味の境界
Martin Fowler bliki が引用される最も多い定義:
A Bounded Context is a central pattern in DDD. It’s the focus of DDD’s strategic design section which is all about dealing with large models and teams.
実用上の意味は、「1 つのモデルが意味を持つ範囲」。例えば「Customer」という単語は:
- 販売コンテキスト では「注文を出す相手」
- サポートコンテキスト では「問い合わせを送ってくる人」
- 会計コンテキスト では「請求書の宛先」
3 つの “Customer” は別物として設計し、必要なら ID で連携する。同じ DB テーブルで強引に統合してはならない。1 つの Bounded Context = 1 つのモデル = 1 つの ubiquitous language。
DB 設計への含意:Bounded Context が異なれば、DB を分けてもよい(Polyglot Persistence の根拠の一つ)。
Aggregate ─ 整合性の境界
Bounded Context の中で、1 トランザクションで一貫性を保つ単位が Aggregate。
graph TB
subgraph "Bounded Context: 注文管理"
subgraph "Aggregate: Order"
O[Order Root]
OI1[OrderItem 1]
OI2[OrderItem 2]
O --> OI1
O --> OI2
end
subgraph "Aggregate: Customer"
C[Customer Root]
A[Address]
C --> A
end
end
O -.参照のみ.-> C
style O fill:#e1f5ff
style C fill:#fff4e1
ルール:
- Aggregate Root(AR) が外部からのアクセスポイント。内部の子オブジェクトは AR 越しにのみ触れる
- 1 トランザクションで変更するのは 1 Aggregate のみ(Vernon, Evans の原則)
- 別 Aggregate との関係は ID 参照 で表現し、
finalconsistency で扱う
Vernon が『実践 DDD』で繰り返し強調するのは「Aggregate を小さく保て」。理由:トランザクション境界が大きくなると、ロック競合・dead lock・スループット低下を招く。
Repository ─ 永続化の隠蔽
Aggregate を永続化する インターフェース。
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
remove(id: OrderId): Promise<void>;
}
ドメイン層は OrderRepository というインターフェースだけを知っている。実装は PostgresOrderRepository でも DynamoOrderRepository でも、テスト用の InMemoryOrderRepository でもいい。
この抽象が “DB は詳細” の物理的な実装になる。
“DB は詳細” の本当の意味
ここまでの三層を整理すると:
graph TB
D[ドメイン層: Aggregate]
D --> R[インターフェース層: Repository]
R --> I1[インフラ層: PostgresOrderRepository]
R --> I2[インフラ層: DynamoOrderRepository]
R --> I3[インフラ層: InMemoryOrderRepository]
style D fill:#e1f5ff
style I1 fill:#ffe1e1
style I2 fill:#ffe1e1
style I3 fill:#ffe1e1
ドメインから見ると、Postgres も DynamoDB も全て「save() ができる Repository」でしかない。永続化技術を入れ替えても、ドメイン層のコードは変わらない。これが Robert Martin の “The Database Is a Detail” の本来の意味。
だが詳細は牙を剥く
理想図はここまで。現実は綺麗にいかない。
牙 1:Aggregate サイズとトランザクション境界
「1 Aggregate = 1 トランザクション」のルールは、実装する DB の特性に依存する。
- PostgreSQL: 単一 DB 内なら自由に複数行 atomic update
- DynamoDB: 単一 item は atomic、複数 item は
TransactWriteItemsで 25 件まで - Spanner: 地理跨ぎでも atomic だが、レイテンシは数百 ms
例:「Order Aggregate と Inventory Aggregate を atomic に更新したい」と思った時、Postgres なら 1 トランザクションでやれる。DynamoDB だと TransactWriteItems の制約に当たり、Aggregate の境界を再検討せざるを得ない。Aggregate を小さく保てという DDD の原則は、DynamoDB ではより強い圧として現れる。
牙 2:Repository の save() の意味
save(order) の中では何が起きているか?
async save(order: Order): Promise<void> {
// PostgreSQL 実装
await this.db.transaction(async (tx) => {
await tx.update('orders', { ... });
for (const item of order.items) {
await tx.upsert('order_items', { ... });
}
});
}
Postgres ならこれは atomic。だが DynamoDB の場合:
async save(order: Order): Promise<void> {
// 単一 item にネストして書く(item サイズ制限 400KB)
await this.client.put({
PK: `ORDER#${order.id}`,
items: order.items, // ← ネストして 1 item に
...
});
}
order.items を別 item にすると atomic 性が崩れる。だから Aggregate のシリアライズ形態が DB に従属する。これは「DB は詳細」の理想と矛盾する現実。
牙 3:クエリ要件と Repository の限界
Repository は CRUD インターフェース。だが「過去 30 日の売上を商品カテゴリ別に集計」のようなクエリは、Repository の責務にハマらない。
DDD の純粋主義では、こうしたクエリは「Read Model」として別途用意する(CQRS の根拠の一つ)。だが Read Model の実装は、結局 OLAP DB(ClickHouse / BigQuery)か Materialized View に行き着き、ドメインから独立した DB が出現する。
「DB は詳細」と言いつつ、複数の DB が現実には居る。
DDD と DB の関係を整理し直す
graph LR
subgraph "ドメイン層が要求すること"
A1[Aggregate の整合性]
A2[Bounded Context の独立]
A3[Read Model の供給]
end
subgraph "DB の特性が決めること"
B1[Trans 境界の大きさ]
B2[複数 DB を持てるか]
B3[Read Model の実装]
end
A1 -.制約される.-> B1
A2 -.実現可能性.-> B2
A3 -.供給形態.-> B3
style A1 fill:#e1f5ff
style A2 fill:#e1f5ff
style A3 fill:#e1f5ff
style B1 fill:#ffe1e1
style B2 fill:#ffe1e1
style B3 fill:#ffe1e1
ドメイン層は 要求を持つ。DB は 能力を持つ。両者の合わせ目で設計の判断が生まれる。「DB は詳細」というのは、詳細の選択を遅らせる権利を持つという意味であって、永続化の特性を無視できるという意味ではない。
この章の要点
- Bounded Context = 言語と意味の境界。異なる Context は DB を分けてよい
- Aggregate = 整合性の境界。1 トランザクションで変更するのは 1 Aggregate
- Repository = 永続化の抽象インターフェース。実装を差し替え可能にする
- “DB は詳細” の本当の意味は「ドメインが永続化に従属しない」こと
- 現実には永続化の特性が Aggregate のサイズや Read Model の実装に逆方向の制約を生む
次章への問いかけ
ドメイン側が 要求 を出すなら、DB 側は 能力 を提示する側だ。
その能力をどう測るか。次章でワークロードを 6 軸で整理し、各 DB がどの軸の組み合わせに最適化されているかを地図にする。