(1)コンストラクタで十分な場合
生成処理がシンプルな場合には、コンストラクタを利用してクライアント側からオブジェクトを生成します。一般的な生成の仕組みですので、理解しやすいと思います。
(2)集約ルートに実装するファクトリ
逆に、生成が複雑な場合は、ファクトリを使用します。例えば、多態性(ポリモーフィズム)を用いて実クラスを決定する場合や、他の集約を組み立てる場合です。なお、ファクトリでしか生成させない場合、クラスのコンストラクタには外部から生成させないスコープの修飾子をつけます。コンストラクタと異なり、ファクトリでは複雑な生成を行うことができ、ユビキタス言語にて表現することができるメリットがあります。ドメインモデラーが話す言葉に注目し、生成するオブジェクトに適した集約にファクトリを用意します。
集約上のファクトリのコード例
SaaSOvationのコードを見てみましょう。ここでは集約であるForumクラスのStartDiscussionForというファクトリを呼び出し、戻り値としてDiscussionオブジェクトを戻しています。これは「投稿者はフォーラム上でディスカッションを開始する」というユビキタス言語を表現していることが分かります。
namespace SaaSOvation.Collaboration.Domain.Model.Forums
{
public class Forum : EventSourcedRootEntity
{
// ディスカッションを開始する(ファクトリ)
public Discussion StartDiscussionFor(ForumIdentityService forumIdService,
Author author, string subject, string exclusiveOwner = null)
{
AssertOpen();
return new Discussion(
this.tenantId,
this.forumId,
forumIdService.GetNextDiscussionId(),
author,
subject,
exclusiveOwner);
}
// 不変条件の確認(フォーラムがオープンしているか)
void AssertOpen()
{
if (this.closed)
throw new InvalidOperationException("Forum is closed.");
}
}
}
このコードでは、まず、整合性が正しくないモデルを生成しないように「不変条件」をチェックするAssertOpenというメソッドを呼び出しています。その後、ディスカッションを開始するために、Discussionクラスのコンストラクタを呼び出しています。コンストラクタを呼び出す時に、ファクトリの引数だけではなく、集約「フォーラム」の情報(this.tenantIdとthis.forumId)を使っていることが分かります。このように集約にファクトリを用意することで、引数の数を減らすことができます。
不変条件を満たすファクトリ
上記のコードでは不変条件をチェックする処理が、ファクトリの最初で呼び出されていました。エヴァンス氏はファクトリの役割として次のことを述べています。
集約全体をひとまとまりとして生成し、その不変条件を強調すること
DDDのファクトリにおいては「一貫した状態のオブジェクトだけを返す」という制約があります。利用者は、複雑な生成ロジックを意識することなく、簡単に正しいオブジェクトを取得できます。その際に、生成される集約の状態が一貫して正しいこと、つまり、不変条件が正しく守られていることが重要な役割となります。このように、ファクトリは生成するオブジェクトに対して、アトミック性(原子のように、それ以上細かい単位や要素に分割されないこと。すべて完了するか1つも実行されないこと)を保ち、中途半端に生成されないことを約束します。
ここまで、集約に実装するファクトリについて紹介しました。
