24

这个问题中,有人回答“你永远不要让域对象实现自己调用服务!”。这个声明是 DDD 的硬性规则还是取决于您自己的应用程序和架构?

人为的例子:

例如,假设我们UserImage的模型中有一个对象,该对象由用户从上传的图像中填充。然后假设我们可以将此图像提交给第 3 方服务,该服务可以识别指纹并Guid在找到匹配项时返回 a。

public IThumbPrintService {
    Guid FindMatch(Bitmap image);
}

public class UserImage {
    public Bitmap Image {get; set;} 
    public Guid ThumbPrintId {get; set;}
    public bool FindThumbPrintMatch() {
       // Would you call the service from here?
       ThumbPrintId = _thumbPrintService.FindMatch(this.Image);
       return ! ThumbPrintId.CompareTo(Guid.Empty);
    }
}

public class RoboCopUserImageService : IUserImageService {
     // Or move the call to a service method 
     // since it depends on calling a separate service interface
     public bool FindThumbPrintMatch(UserImage userImage) {
        userImage.ThumbPrintId = _thumbPrintService.FindMatch(userImage.Image);
        return !userImage.ThumbPrintId.CompareTo(Guid.Empty);            
     }
}

不让域对象自己调用服务可以避免或获得什么?

编辑:有没有讨论这个特定主题的好的在线文章?

4

4 回答 4

47

这是电子表格难题电话是拨打电话号码,还是电话号码在电话上自行拨打

您可能会发现Double Dispatch 读起来很有趣,尽管我认为在您的情况下有点矫枉过正

单一职责原则经常与 OO 原则“告诉,不问不一致。我对这个主题的感觉摇摆不定,当逻辑应该进入域对象时,我已经确定了以下条件:

在您的情况下,我选择不将对服务的调用放在实体对象中,主要是因为该服务似乎与您的域无关,而与持久性更相关。域对象应该与域概念耦合,我认为您提供的服务不合格。

我认为在实体中调用服务可能是可以接受的一个例子是,如果您的应用程序使用第三方工作流服务器来管理其部分状态。本质上,这是在运行时定义状态的状态模式

我认为让 domainObject.moveToNextState() (假设此代码在您的通用语言中“有意义”)调用与您的服务器对话的服务是可以接受的,因为工作流服务器管理域模型的一部分。

我要补充一点,DDD 对遵循该领域的语言非常感兴趣。您是否听到领域​​专家说“用户图像会发现其指纹是否与 XYZ 供应商服务中的指纹匹配”?还是他们说“给定指纹的 XYZ 供应商服务表明该指纹是否存在”?选择在您的领域中最有意义的那个。

还有一些想法(我已经考虑了很多这个问题,因为它是设计的核心):

  • 在 Evans DDD 书中,Account 实体具有 credit(Amount)、debit(Amount)、transferTo(Account, Amount) 和 accrue() 等方法,但 FundsTransferService 具有 transfer(Account, Account, Amount) 方法。transferTo 方法不调用任何服务,而只是处理涉及 Accounts 的逻辑,例如贷记和借记正确的金额。

    除了协调之外,FundsTransferService 有自己的规则要检查,这些规则不适合 Accounts。贷记或借记的确切金额可能涉及外部各方。这使得 transferTo 调用服务很尴尬。

  • 对于像 UserImage 这样的简单对象,可以适合对象本身的重要域逻辑可能是稀缺的,因为据我所知,它不是聚合。我认为聚合提供了更多容纳领域逻辑的机会。Account 示例很可能是一个聚合。
于 2009-01-12T23:20:03.800 回答
2

我看到的一个缺点是,允许您的域对象调用服务可能会使序列化变得更加困难,或者至少在另一方的某人调用其服务方法时序列化后会导致一些问题。

于 2009-01-12T21:38:41.243 回答
0

如果您允许实体对象调用服务,它将执行数据对象和服务对象两个角色。一般来说,每个对象都应该有责任,不仅在实现上,而且在使用上。

在您的情况下,低级 UserImage 似乎既是图像又是指纹识别器。

于 2009-01-12T21:45:19.733 回答
0

我认为最好不要从实体或值对象调用存储库或服务,但有时这是必要的,例如,如果一个实体必须返回应该从数据库加载但它不能使用对象图导航到它的其他实体。然后依赖倒置原则就派上用场了,这意味着实体和值对象依赖于服务和存储库的接口,而不是实现。

于 2017-08-03T22:06:45.220 回答