域驱动设计-实体问题中的外部依赖关系


22

我想启动域驱动设计,但是在开始之前我想解决一些问题:)

假设我有一个网上论坛和用户,当用户想要加入网上论坛时,我正在调用groupsService.AddUserToGroup(group, user)method。在DDD中,我应该这样做group.JoinUser(user),看起来不错。

如果我有一些添加用户的验证规则,或者将用户添加到组中时需要启动一些外部任务,则会出现问题。拥有这些任务将导致实体具有外部依赖性。

例如-限制用户最多只能参加3个小组。这将需要从group.JoinUser方法内部进行DB调用来验证这一点。

但是,实体依赖于某些外部服务/类这一事实在我看来并不那么好,也不是“自然的”。

在DDD中处理此问题的正确方法是什么?

Answers:


15

假设我有一个Groups和Users,当用户想要加入一个组时,我正在调用groupsService.AddUserToGroup(group,user)方法。在DDD中,我应该做group.JoinUser(user),看起来不错。

但是,如果手头的任务过于复杂或不适合实体模型,则DDD还鼓励您使用(无状态)服务来执行任务。可以在域层中提供服务。但是域层中的服务应仅包含业务逻辑。另一方面,外部任务和应用程序逻辑(例如发送电子邮件)应在应用程序层中使用域服务,例如,在其中可以使用单独的(应用程序)服务来包装它。

如果我有一些添加用户的验证规则,则会出现问题...

验证规则确实属于域模型!它们应封装在域对象(实体等)中。

...或将用户添加到组时需要启动一些外部任务。拥有这些任务将导致实体具有外部依赖性。

虽然我不知道您在说什么外部任务,但我认为这就像发送电子邮件等。但这实际上不是您的域模型的一部分。它应该驻留在应用程序层中,并在那里恕我直言。您可以在应用程序层中拥有一个服务,该服务在域服务和实体上运行以执行这些任务。

但是,实体依赖于某些外部服务/类这一事实在我看来并不那么好,也不是“自然的”。

这是不自然的,不应该发生。实体不应该知道不属于其职责的东西。服务应用于协调实体交互。

在DDD中处理此问题的正确方法是什么?

在您的情况下,该关系应该是双向的。用户是加入组还是组带用户取决于您的域。用户是否加入该组?还是将用户添加到组?它在您的域中如何运作?

无论如何,您具有双向关系,因此可以确定用户集合中用户已经属于的组的数量。确定负责任的班级后,从技术上讲是将用户传递给组还是将组传递给用户都是不重要的。

然后,验证应由实体执行。整个事情是从应用程序层的服务调用的,该服务还可以做技术性的工作,例如发送电子邮件等。

但是,如果验证逻辑确实很复杂,则域服务可能是更好的解决方案。在这种情况下,将业务规则封装在那里,然后从应用程序层调用它。


但是,如果我们将如此多的逻辑移到实体之外,应该保留在里面什么呢?
SiberianGuy

实体的直接责任!例如,如果您可以说“用户可以加入组”,则这是用户实体的责任。有时,出于技术原因,您必须权衡决策。我也不是双向关系的忠实拥护者,但有时它最适合该模型。因此,在谈论域时请仔细听。“一个实体可以...”“该实体可以...”当您听到这样的句子时,这些操作很可能属于该实体。
猎鹰

此外,当两个或两个以上其他不相关的对象需要参与一项任务以完成某项工作时,您就知道需要服务。
猎鹰

1
谢谢您的回答,猎鹰!顺便说一句,我一直试图使用无状态服务,所以我离DDD更近了:)假设在一个域中,此UserJoinsToGroup操作属于Group。问题是,要验证该操作,我需要知道用户已经参与了多少组(如果操作已经> 3,则拒绝该操作)。要知道我需要查询数据库。我该如何从组实体中做到这一点?我还有更多示例,当我需要在自然应该属于实体的操作中接触数据库时(如果需要,我将其张贴:))
Shaddix

2
好吧,如果我考虑一下:那么一个GroupMembership实体呢?它可以由工厂构造,并且该工厂可以访问存储库。那将是不错的DDD,并且封装了成员资格的创建。工厂可以访问存储库,创建成员资格,然后分别将其添加到用户和组。这个新实体也可以封装特权。也许这是个好主意。
猎鹰

3

我处理验证问题的方法是这样的:创建一个名为的域服务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


1
您可能需要考虑格式化帖子中的源代码以提高可读性
Benni 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.