Curated Tech Reading Map

次に読むべき技術書が見つかるサイト

ブログ記事

TypeScriptで学ぶDDDの実装:集約をIDで参照するという設計判断の意味

著者: DevBookPath 編集部公開日:

「ドメイン駆動設計(DDD)は概念が難解で、実際にどうコードに落とすかが分からない」——この壁に当たったことがある開発者は多い。山下祐也・著、増田亨・監修の『つくりながら学ぶ!ドメイン駆動設計 実践入門』は、オンライン書店の開発をTypeScriptで実装するという具体的なコードを通して、その「どうコードに落とすか」に答えを出そうとしている。

1. アーキテクチャの型よりもドメインを先に

クリーンアーキテクチャのフォルダ構成を整えてから開発を始める——この順序に問題がある、と本書は示唆する。

フォルダ構成の整合性はツールや規約が保証できる。しかしビジネスのルール(ドメイン)をどのクラスに持たせるかは、チームがドメインを理解しているかどうかで決まる。本書が最初に扱うのはドメインの定義であり、インフラの設定ではない。

「DDDは手順書ではなく、考え方」という立場が実装例の随所に表れている。

2. 集約を小さく保つ——ID参照という設計判断

DDDを学ぶ開発者が最初に直面する難問の一つが「集約(Aggregate)の境界をどこに引くか」だ。

ORMに慣れた開発者は、関連するオブジェクトをすべてネストして表現しがちだ。商品クラスが注文リストを持ち、注文クラスがレビューを持つ——という構造は直感的だが、問題を抱える。

  • 商品を1件読み込む際に、関連する何百件もの注文データが一緒にロードされる
  • 別々のユーザーが異なる変更を同じ集約に加えようとすると、トランザクション競合が起きやすくなる

本書の解決策は「他の集約への参照はIDで持つ」というルールだ。Order クラスは Product オブジェクトを直接持たず ProductId だけを持つ。必要な時に必要なデータだけ取得する。

この設計はコーディング規約ではなく、アーキテクチャ上の判断だ。後からマイクロサービスへ分割する際の境界になり、分散システムでの独立したデプロイを可能にする。

3. 値オブジェクトで「貧血モデル」を脱する

ゲッターとセッターだけを持つデータの入れ物クラスは「貧血モデル」と呼ばれる。ビジネスロジックを持たないため、利用側があちこちでバリデーションや計算を行うことになる。

値オブジェクトは逆のアプローチをとる。「商品名は1文字以上50文字以下でなければならない」というルールを ProductName クラスのコンストラクタで保証する。TypeScriptの静的型システムを利用して、不正な状態を「型として表現不可能」にする。

class ProductName {
  private constructor(private readonly value: string) {}

  static create(name: string): ProductName {
    if (name.length < 1 || name.length > 50) {
      throw new Error('商品名は1〜50文字で入力してください');
    }
    return new ProductName(name);
  }
}

このクラスを string の代わりに引数型として使えば、コンパイル時に渡し間違いを検出できる。

4. 複数集約にまたがる整合性——Outboxパターン

集約を小さく分割すると、複数の集約を一度のトランザクションで更新する「即時整合性」を保ちにくくなる。

例えば、注文確定時に「注文の保存」と「メール通知の送信」の両方が必要な場合、DBへの保存は成功したが通知が失敗するケースへの対応が必要になる。

Outboxパターンはこの問題に対処する設計だ。アプリケーションはDBへの書き込みと同じトランザクション内に「送信すべきイベント」を専用テーブル(Outbox)へ記録する。別プロセスがそのテーブルを監視して非同期にメッセージを送信することで、「DBへの保存が成功したなら、いつかかならずイベントが発火する」という保証を作る。

難易度は高いが、本書はこのパターンをステップを追って実装例として示している。

DevBookPath のマップで確認する

この本の前後の読書順は、DevBookPath のグラフで確認できます。

👉 ソフトウェア設計の地図を見る


本記事のリンクには Amazon アソシエイト等の広告が含まれる場合があります。リンク経由の購入で運営者に紹介料が支払われることがあります。

この記事を共有

この地図を共有