little hands' lab

ドメイン駆動設計、アジャイルプラクティスを実践し、解説しています。

DDDにおけるドメイン層オブジェクト設計の基本方針[ドメイン駆動設計]

株式会社ログラスの松岡(@little_hand_s)です。

ドメイン層のオブジェクトを設計する際に、重要な基本方針があります。

  • ドメインモデルの知識を対応するオブジェクトに書く
  • 常に正しいインスタンスしか存在させない

この2つを守ると、非常に保守性の高いコードにすることができます。 以下、詳細に解説します。

ドメインモデルの知識を対応するオブジェクトに書く

ドメイン知識(ルール/制約)を表現する実装を、ドメイン層のオブジェクトに寄せていきます。 この判断は、「ドメインモデル図に書かれた吹き出しの内容が、どの層で実装されているか」という基準に基づき行います。

この基準はコード設計の指針として非常に役立ちます。 設計の良し悪しというのはさまざまな基準があるため、レビューをしていてもいわゆる「俺の考えた最強の設計」同士が戦ってしまうことがあります。 しかし、「ドメイン知識はドメイン層に書く」というルールをコード規約のように定めれば、それに従っているかどうかは機械的に判断できます。

設計時にも、レビュー時も、同じ規約に基づいて判断できるので、アプリケーション全体に規律を作ることができます。

常に正しいインスタンスしか存在させない

もし、ドメインオブジェクト(ドメインモデルを表現するオブジェクト)が、整合性の保証されたインスタンスしか存在できなくなったらどうなるでしょうか。 インスタンスをどのタイミングで永続化しても、確実にデータの整合性が保証されることになります。 これは実装上非常に強い安心感に繋がります。

これを実現するためには、次の2つを行います。

  • 生成条件の強制
  • ミューテーション条件の強制

以下、それぞれ解説します。

サンプルコード

    @Getter
    // @Setter   // ←Setterは無くす ④
    public class Task {
      private Long id;
      private TaskStatus taskStatus;
      private String name;
      private LocalDate dueDate;
      private int postponeCount;
    
      // コンストラクタ: 新規登録時の処理
      public Task(String name, LocalDate dueDate) {
        if (name == null || dueDate == null) {
            throw new IllegalArgumentException("必須項目が設定されていません");
        }
        this.name = name;
        this.dueDate = dueDate;
        this.taskStatus = TaskStatus.UNDONE; // ①
        this.postponeCount = 0;
      }
    
      private static final int POSTPONE_MAX_COUNT = 3; // ②
    
      // 延期時の処理
      public void postpone() { // ③
        if (postponeCount >= POSTPONE_MAX_COUNT) {
          throw new IllegalArgumentException("最大延期回数を超過しています");
        }
        dueDate.plusDays(1L);
        postponeCount++;
      }
    }

生成条件の強制

すべてのインスタンスはコンストラクタ、もしくはファクトリーメソッド(以下、2つ合わせて生成メソッドとする)を経由して生成されます。 そのため、存在する生成メソッドがすべて正しい条件を強制できれば、新規作成されたインスタンスはすべて整合性が保証されていることになります。

これに反しているのが、何もロジックのないデフォルトコンストラクタです。 すべての項目に値が入っていないインスタンスは、整合性を保証されたものではありません。 習慣的にデフォルトコンストラクタを放置してしまいがちですが、 デフォルトコンストラクタを無くし、意味のある生成メソッドだけ存在するようにします。

サンプルコードのTaskクラスでは、コンストラクタが1つだけあり、デフォルトコンストラクタはありません。 そのため、生成されるインスタンスはすべてこのコンストラクタを確実に通ることがわかります。

ミューテーション条件の強制

生成メソッドの制御により、インスタンスが正しい状態で生成されることが保証できました。 あとは、すべての内部状態の変更、つまりミューテーションが正しければ「常に正しいインスタンスしか存在させない」ことが可能になります。 そのために、正しいミューテーションを起こすメソッド(以下、ミューテーションメソッド)のみ外部に公開するようにします。

これに反しているのが、すべての項目に対するセッターです。

サンプルコードのTaskクラスにはセッターがなく、ミューテーションメソッドはpostponeメソッドしかありません。 コンストラクタを合わせて判断すると、Taskクラスは正しいインスタンスしか存在できないことがわかります。 このことが複数クラスを追いかけることなく、たった1クラスで判断できるということは、可読性、保守性向上のために大きな効果があります。

コードを含めたイメージ

こちらのスライド94ページ以降をご覧ください、具体的なコード例を掲載しています

www.slideshare.net

筆者執筆書籍

初めてDDDを学ぶ方、もしくは実際に着手して難しさにぶつかっている方向けの書籍です。 迷子になりがちな「DDDの目的」や「モデル」の解説からはじめ、具体的なモデリングを行い実装まで落とす事例を元に、DDDの魅力や効果を体感することを目指します。

little-hands.booth.pm

本書はDDDの重要トピック「モデリング」「集約」「テスト」について詳細に解説し、その他のトピックでは頻出の質問への回答と具体的なサンプルコードをふんだんに盛り込みました。現場で実践して、困っていることがある方はぜひこちらもご覧ください。

little-hands.booth.pm