目次を表示する

DB 設計の軸 2026 ─ ドメイン駆動と特性駆動の二つの流派を行き来する 19 章

ドメイン視点の地図 ─ Aggregate / Bounded Context / Repository

ドメイン視点の地図 ─ 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 参照 で表現し、final consistency で扱う

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 がどの軸の組み合わせに最適化されているかを地図にする。