ドメイン駆動設計を開始したいのですが、開始する前に解決したい問題がいくつかあります:)
グループとユーザーがいて、ユーザーがグループに参加したいときに、groupsService.AddUserToGroup(group, user)
メソッドを呼び出しているとしましょう。 DDDではgroup.JoinUser(user)
を実行する必要があります。
ユーザーを追加するための検証ルールがある場合、またはユーザーがグループに追加されたときに外部タスクを開始する必要がある場合、問題が発生します。これらのタスクがあると、エンティティは外部依存関係を持つことになります。
たとえば、ユーザーが最大3つのグループにのみ参加できるという制限があります。これを検証するには、group.JoinUserメソッド内からのDB呼び出しが必要です。
しかし、エンティティが一部の外部サービス/クラスに依存しているという事実は、私にとってはあまり良くも「自然」でもないようです。
DDDでこれに対処する適切な方法は何ですか?
グループとユーザーがいて、ユーザーがグループに参加したいときに、groupsService.AddUserToGroup(group、user)メソッドを呼び出しているとしましょう。 DDDでは、group.JoinUser(user)を実行する必要があります。
しかし、DDDでは、手元のタスクが複雑すぎる場合やエンティティモデルに適合しない場合に、(ステートレス)サービスを使用してタスクを実行することも推奨しています。ドメイン層にサービスを配置しても問題ありません。ただし、ドメイン層のサービスにはビジネスロジックのみを含める必要があります。一方、外部タスクとアプリケーションロジック(電子メールの送信など)では、アプリケーションレイヤーのドメインサービスを使用する必要があります。たとえば、別の(アプリケーション)サービスでラップすることができます。
ユーザーを追加するための検証ルールがある場合、問題が発生します...
検証ルールはドメインモデルに属しています!それらはドメインオブジェクト(エンティティなど)内にカプセル化する必要があります。
...またはユーザーがグループに追加されたときに、いくつかの外部タスクを開始する必要があります。これらのタスクがあると、エンティティは外部依存関係を持つことになります。
どのような外部タスクについて話しているのかはわかりませんが、メールの送信などのようなものだと思います。しかし、これは実際にはドメインモデルの一部ではありません。それは、アプリケーション層に存在し、そこで見られるはずです。これらのタスクを実行するためにドメインサービスとエンティティで動作するサービスをアプリケーションレイヤーに含めることができます。
しかし、エンティティが一部の外部サービス/クラスに依存しているという事実は、私にとってはあまり良くも「自然」でもないようです。
それは不自然であり、起こるべきではありません。エンティティはその責任ではないものについて知ってはなりません。サービスは、エンティティの相互作用を調整するために使用する必要があります。
DDDでこれに対処する適切な方法は何ですか?
あなたの場合、関係はおそらく双方向であるべきです。ユーザーがグループに参加するか、グループがユーザーを担当するかは、ドメインによって異なります。ユーザーはグループに参加しますか?または、ユーザーはグループに追加されていますか?それはあなたのドメインでどのように機能しますか?
とにかく、双方向の関係があるため、ユーザーがユーザー集計内で既に属しているグループの数を決定できます。ユーザーをグループに渡すか、グループをユーザーに渡すかは、責任のあるクラスを決定すると、技術的には簡単です。
次に、エンティティによって検証が実行されます。すべてはアプリケーション層のサービスから呼び出され、メールの送信などの技術的なことも実行できます。
ただし、検証ロジックが本当に複雑な場合は、ドメインサービスの方が優れたソリューションである可能性があります。その場合は、そこにビジネスルールをカプセル化し、アプリケーションレイヤーから呼び出します。
検証の問題に取り組む方法は次のとおりです。MembershipService
というドメインサービスを作成します。
class MembershipService : IMembershipService
{
public MembershipService(IGroupRepository groupRepository)
{
_groupRepository = groupRepository;
}
public int NumberOfGroupsAssignedTo(UserId userId)
{
return _groupsRepository.NumberOfGroupsAssignedTo(userId);
}
}
グループエンティティにはIMemberShipService
を注入する必要があります。これは、クラスレベルまたはメソッドレベルで実行できます。メソッドレベルで実行するとします。
class Group{
public void JoinUser(User user, IMembershipService membershipService)
{
if(membershipService.NumberOfGroupsAssignedTo(user.UserId) >= 3)
throw new BusinessException("User assigned to more than 3 groups. Cannot proceed");
// do some more stuff
}
}
アプリケーションサービス:GroupService
は、コンストラクターインジェクションを使用してIMemberShipService
でインジェクトできます。これは、JoinUser
クラスのGroup
メソッドに渡すことができます。