計画と改善

エンジニアのブログ

ドメイン駆動設計入門(ボトムアップでわかる!ドメイン駆動設計の基本)を読んだ

書籍を通じてインターフェースを利用したLayered Architectureの構成を意識してコードが書かれている。

Application層とDomain層にあるrepositoryのインターフェースはInfrastructure層で実装されている。ドメイン層はInfrastructure層に依存することなく、抽象に依存することができる。

大体以下のよく見る図と同じような構成。

Domain層の抽象へ依存の例

class FooDomainService //FooDomainServiceはDomain層
{
    private IFooRepository fooRepository; //抽象に依存

    public FooDomainService(IFooRepository fooRepository) 
    {
        this.Repository = fooRepository
    }
    ...略
}

似たような構成のプロジェクトを経験したことがあるが、当時はDomain層やApplication層になぜInfrastructure層のインターフェースがあるのか理解できなかった。書籍でデータストアをテスト用のものに変更している箇所を確認して、ポリモーフィズムによる振る舞いの変更のメリットが得られることをようやく整理ができた。

アプケーション層でなぜDTOを利用しているのかも、この書籍でわかりやすく解説されていた。ドメイン層にあるオブジェクトを直接渡さずに受け渡すことで、ドメインオブジェクトに対する操作をアプリケーション層に限定するためだった。

以下はアプリケーションサービスでFooエンティティに対してFooDataというDTOに移し替えて返却している例

public class fooApplicationService
{
    private readonly IFooRepository fooRepository

    略...
    
    public FooData Get(string fooId) 
    {
        var targetId = new FooId(fooId);
        var foo = fooRepository.Find(targetId);

        var fooData = new FooData(foo.Id.Value, foo.Name.Value); //ここで移し替えているので返却元ではドメインオブジェクトのメソッドを利用できない
        return fooData;
    }
}

全体的に丁寧に書かれている印象だったが、C#の理解がある程度必要なので、その分学習コストが高かった。

以下ポイントをまとめたところを記載しておく。

ドメイン駆動設計とモデリング

人間の営みをソフトウェア上で全て表現することは難しい。

そのため、概念を抽象化、取捨選択しする必要がある。この作業のことをモデリングと呼び、その結果として得られるものがモデルである。

ドメイン駆動設計ではドメインの概念をモデリングしたものをドメインモデルと呼んでいる。

そして、ドメインモデルをソフトウェア上で動作するモジュールとして表現したものが、ドメインオブジェクト。

開発者はドメイン知識に詳しいわけではなので、ドメイン知識に詳しい人(ドメインエキスパート)とともにソフトウェアを協力して作り上げる必要がある。

値オブジェクト

ドメイン固有の概念を値として表現するもの。属性によって識別される。

値オブジェクトは不変で変更されてはならない。

そのため、値オブジェクト同士を比較する時は属性にはアクセスはできず、値オブジェクト自体を比較する。

C#だとオブジェクトの比較をObject.Equalsで行なってしまうと参照の比較になってしまうため、値オブジェクト内部で比較用のEqualsを実装する必要がある。

その場合、値オブジェクトにIEquatable<T>インターフェースを実装して、IEquatable<T>.Equals(T)メソッドを作成し、objectを比較するObject.EqualsGetHashCodeをオーバーライドして実装する。

どのようなオブジェクトを値オブジェクトとして定義するのかは考えどころではある。

値に対して業務上のルールが存在している場合は値オブジェクトとして、コンストラクタにルールを定義しておくと、値オブジェクト生成時に異常値が入っていた場合は例外をスローすることなどができるので、基準の一つとして考えられる。(これはアプリケーションサービスなどにドメインロジックが漏れ出ることも防ぐことができ、再利用性も高まる)

エンティティ

エンティティは値オブジェクトとは違い、属性ではなく同一性によって識別される。

エンティティは可変で、属性が同じでも区別される。

エンティティは同一性を識別するための識別子を持つ。

同一性を比較するためのふるまいの実装は値オブジェクトと同様にIEquatable<T>インターフェースを実装するなどして行い、同一かどうかは識別子が同じかどうかで判断する。

ドメインサービス

値オブジェクトやエンティティではうまく表現しきれない知識を扱う。

重複ロジックはドメインエンティティに実装してしまうと、自分自身に問い合わせをしてしまうことになるので、ドメインサービスで取り扱うことができる。

ドメインオブジェクトのソースコードをみて、業務仕様が理解できる状態であることが望ましいため、業務ロジックは基本的に、値オブジェクトやエンティティに記述するべきである。

そのため、ドメインサービスはそれらで表現できないやむを得ない場合にのみ使用する。

リポジトリ

データの保存や復元といった永続化、再構築を担当するオブジェクト。

ドメインオブジェクトにデータを扱うロジックを書いてしまうと、業務ロジックがぼやけてしまう。また反対にリポジトリに業務ロジックを書いてしまうと、ドメイン層からドメイン知識が失われてしまう。そのため、データを扱うオブジェクトとしてリポジトリを用意することで、業務ロジックを扱うオブジェクトとデータを扱うオブジェクトとで役割を分担する。

必要に応じてデータストアをテスト用に入れ替えたりして利用する。

アプリケーションサービス

値オブジェクト、エンティティ、ドメインサービス、リポジトリを操作してアプリケーションを作り上げるためのオブジェクト。

アプリケーション層に所属するが、ここでもドメイン知識に関することは書かないようにする。

また、リポジトリから取得したドメインモデルはそのままクライアントへ返却せずに、DTOに移し替えて返却を行う。そうすることでドメインオブジェクトのメソッドをアプリケーション層以外で呼び出せないようにできる。

更新の際はcommandオブジェクトを利用することで、アプリケーションサービスのメソッドのシグネチャを変更しないことも可能。

ファクトリ

オブジェクトを作ることに特化したオブジェクト。コンストラクタで生成するには複雑なオブジェクトを生成するのに利用する。

感想

オブジェクト指向でなぜ作るのかを読んだ後に読むにはちょうど良いレベル感の本だった。

概念的な説明よりもどのようにコード上で実現するのかを重視して書かれていて、イメージが湧きやすかった。