领域对象和服务

27
这个问题中,有人回答说:“您永远不应该让域对象实现自己调用服务!”这是DDD的严格规定还是取决于您自己的应用程序和架构?
虚构的例子:
例如,假设我们的模型中有一个UserImage对象,它由用户上传的图像填充。然后,假设我们可以将此图像提交给第三方服务,该服务可以识别指纹并返回Guid(如果找到匹配项)。
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个回答

50

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

你可能会觉得双重分派很有趣,尽管在你的情况下可能过度了

单一职责原则常常与面向对象准则告诉,不要询问相互矛盾。我对这个问题的感觉一直在摇摆不定,现在我已经确定了以下逻辑应该放入领域对象的条件:

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

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

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

我会补充说明,DDD非常关注领域语言。您是否听到领域专家说“用户图像会查找其指纹是否与XYZ供应商服务中的指纹相匹配”?还是他们会说:“给定一个指纹,XYZ供应商服务指示该指纹是否存在”?选择在您的领域中最有意义的那个。
以下是更多想法(我已经思考了这个问题很多,因为它是设计的核心):
- 在Evans DDD书中,一个Account实体有像credit(Amount),debit(Amount),transferTo(Account,Amount)和accrue()这样的方法,但是FundsTransferService有一个transfer(Account,Account,Amount)方法。 transferTo方法不调用任何服务,而仅处理涉及账户的逻辑,例如正确地记账和转账。 FundsTransferService除了协调外,还有自己的规则要检查,这些规则不适用于Accounts。确切的信贷或借方金额可能涉及外部方。这使得transferTo调用服务变得棘手。
- 对于像UserImage这样的简单对象,可以放在对象本身中的重要领域逻辑可能很少,因为据我所知,它不是聚合。我认为,聚合呈现了更多容纳领域逻辑的机会。 Account示例可能是一个聚合。

谢谢,电子表格难题现在让这个问题更加清晰了。我也喜欢你建议向领域专家口头表述问题以确定哪种方法更有意义的方式。 - Todd Smith
1
哇,太棒了,非常清晰,非常详细。我想给你超过一个赞! - Guillaume
1
谢谢夸奖。这个回答的内容实际上是我未被诊断出的强迫症效应的综合体。DDD和OO所提出的问题总能让我着迷。 - moffdub

2

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


0
如果允许实体对象调用服务,那么它将执行数据对象和服务对象两个角色。一般来说,每个对象都应该有一个职责,不仅在实现上如此,在使用上也是如此。
在您的情况下,卑微的UserImage似乎既是图像又是指纹识别器。

这个例子是即兴编造的。真正的问题是你的领域对象是否应该调用服务,而电子表格难题几乎正中要害。这取决于具体情况! - Todd Smith
感谢您澄清您的答案。如果您的实体对象有一个方法,例如Account.Debit(amount),需要使用服务来完成操作(例如更新存储库、调用验证服务或发送通知),该怎么办? - Todd Smith
它将通过使AccountService接受一个简单的Account对象作为debit方法的参数来处理它。 - Allain Lalonde

0

我认为最好不要从实体或值对象中调用存储库或服务,但有时候这是必要的。例如,如果一个实体需要返回另一个实体,而这个实体应该从数据库加载,但它不能使用对象图遍历到它,那么依赖反转原则就会发挥作用。这意味着实体和值对象依赖于服务和存储库的接口,而不是实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接