プログラミング言語にはプリミティブな型が用意されています。プリミティブな型だけを組み合わせてシステムを開発することはもちろん可能ですが、システムが持つ固有な値を定義したほうがよい場合があります。こういった固有な値を定義したものを値オブジェクトと呼んでいます。これらの値オブジェクトですが不変である
、 交換が可能である
、等価性によって比較される
といった性質があります。これらの性質を持つ値オブジェクトを定義すべきかは開発者が判断します、そのデータが値オブジェクトにあたいする価値があると思うのであれば作成していくべきです。
また値オブジェクトで重要なことは独自の振る舞いを定義できます。例えば通貨の計算であれば、単位を間違えたりが考えられます。これはプリミティブな型で+演算子を使うと発生しますが、値オブジェクトの独自の振る舞い(メソッド)を定義して計算するようにすれば、このような単位間違いを防ぐことができます。
その他にも値オブジェクトを使うと表現力が増す(string だとただの文字列(xxxx-yy-zzz)だが、値オブジェクトで定義するとその各項目(x, y, z)が何を表しているのか表現できる)、不正な値を存在させない、誤った代入を防ぐ、ロジックの散在を防ぐなどの役割があります。
ドメイン駆動設計におけるエンティティはドメインモデルを実装するドメインオブジェクトになります。値オブジェクトもドメインオブジェクトになりますが、それらの違いは属性によってではなく同一性で区別されるという点です。例えば Person クラス(ドメインクラス)の身長や体重(属性)が変化しても人したら別の人として区別しないと思います、おそらく割り振られたIDで区別することになるでしょう。ドメイン駆動設計においてはこのようなものをエンティティと呼びます。(逆に属性で区別が変わるようなものを値オブジェクトとしているそうです。)これらのエンティティですが可変である
同じ属性であっても区別される
同一性により区別される
といった性質があります。
値オブジェクトやエンティティなどのドメインオブジェクトをそのまま制御すると不自然さが出てくる場合があります。そのようなときはドメインサービスを作成し、その不自然さを解決するようにする。例えばエンティティの重複がないか判別する処理を作るとき、エンティティに重複判定処理を記述しようと思えばできる。しかしこれはものすごく不自然であるので、ドメインサービスを作成して重複判定処理を記述すると自然な処理の実装になる。
不自然は複数の値オブジェクトやエンティティを組み合わせた処理を記述するときにあらわれたりする。例えば重複確認処理は値オブジェクトやエンティティのどちらにでも書くべきではないし、書くと記述したコードが不自然になってします。そのようなときにはドメインサービスを作成し、複数の値オブジェクトやエンティティを組み合わせた処理を自然な処理に変えてやる。
ドメインサービスを定義したときにありがちなのが、本来はドメインオブジェクトに定義すべきふるまいがドメインサービスに定義されてしまうことである。この状態になるとドメインオブジェクトはふるまいを持たずただ値を保持するだけになってしまい、オブジェクト指向の方針であるデータとふるまいをまとめましょうから逆の方向になってします。(データとふるまいが分かれたクラスは利用者によって動作がかわるので、かなり不安定なクラスになります。)
ドメインサービスには値オブジェクトやドメインオブジェクトのふるまいを全て定義しようと思えばできていまう。全て定義しようとしたらどうなるか、値オブジェクトやエンティティは値は保持するだけでなく、ふるまいはドメインサービスが定義するようになってしまう。(このような状態をドメイン貧血症とよぶ)このような状態になるとオブジェクト指向のデータとふるまいをまとめるという方針とは逆になりわかりずらいコードを生み出しがち。
プログラムを終了すると生成されたオブジェクトは消えてしまいます。オブジェクトを繰り返し利用するには何らかのデータストアにオブジェクトを永続化し、再構築する必要があります。オブジェクトを永続化、再構築する際には直接処理を行うのではなく、リポジトリに永続化、再構築を依頼します。
ドメインオブジェクトの組み合わせで実行するようなスクリプトを定義する層。アプリケーションサービスで公開する操作にドメインオブジェクトは公開しないようにしたほうがよい。なぜならドメインオブジェクトには名前を変更するなど、永続化したデータを変更させる操作が含まれているからです。その変更する動作はアプリケーションサービスで操作するもので、ユーザが操作できると困るシチュエーションがあるためです。なのでドメインオブジェクトを公開するのではなくDTOを公開したほうがよい。
複雑な処理を単純な操作としてまとめること
ServiceLocatorと呼ばれるオブジェクトに依存解決先となるオブジェクトを事前に登録しておいて、インスタンスが必要となる場所でServiceLocatorを経由してインスタンスを取得するパターンです。ServiceLocatorは依存関係が外部からみえづらくなる、テストの維持が難しくなることからアンチパターンとされている。
DI コンテナのこと、DIするために必要なインスタンスを管理する。
IDなどの生成処理などはFactoryクラスに任せると、値オブジェクトやエンティティにIDなどの生成処理を記述しなくてよいので、テストなどするときに値オブジェクトやエンティティを用意しやすくなる。
もしエンティティなどのIDの生成処理が入っているとどうなるか、Uuidを利用した生成であればそこまで問題にはならないが、データベースを利用した処理だとかなり問題になる。IDを生成するにはデータベースに接続する必要があるので、エンティティにデータベースに接続するためのインスタンスを用意する必要がでてくる、この場合はエンティティを生成する際にはデータベースが必ずなければならいので、テストなどで面倒が生じてしまう。
なので Factory クラスという 値オブジェクトやエンティティを作成するクラスを作成し、そのなかでIDなどの生成を行うようにする。そうすれば値オブジェクトやエンティティの中にはIDの生成処理を記述せずにすむので、テストなどで適当なIDを作成して、テストを実行したり楽ができる。
IDの生成クラスにFacotryクラスを使うという手もあるが、他にはRepositoryに次のIDを取得する関数をつけるというのもよい。これにはRepositoryにその関数があるのはおかしいという声やそこまで気にするものではないという声の両方がある。IDの生成に関してはプロジェクトごとの流儀があるはずなので、そこは臨機応変に変えるべきだそう。