如何避免贫血领域模型并保持关注点分离?

20

似乎决定让您的对象完全认识其在系统中的角色,并仍然避免领域模型对数据库和服务层有太多依赖?

例如:假设我有一个带有修订历史记录和几个数据引用的“查找表”的实体,您的实体对象应该有从某些查找表获取详细信息的方法,无论是通过提供对查找表行的访问还是通过将方法委托给它们,但为此它依赖于数据库层从这些行读取数据。此外,在保存实体时,它不仅需要知道如何保存自身,还需要保存修订历史记录中的条目。是否有必要将对模型对象传递数十个不同的数据层对象和服务对象的引用?这似乎使逻辑比仅将薄模型传递给服务层对象来回更加复杂,但我听到很多“聪明人”推荐这种结构。

4个回答

19
非常好的问题。我花了很多时间思考这样的主题。
您通过注意到表达领域模型和关注点分离之间的紧张关系表现出了极大的洞察力。这很像我所问的告诉不要询问和单一职责原则的问题中的紧张关系。
以下是我的观点。
领域模型是贫血的,因为它不包含任何领域逻辑。其他对象使用贫血领域对象获取和设置数据。您描述的内容对我来说听起来不像是领域逻辑。它可能是,但通常而言,查找表和其他技术语言最有可能是我们理解的术语,但不一定对客户有什么意义。如果我理解有误,请澄清一下。
无论如何,领域对象的构建和持久化都不应该包含在领域对象本身中,因为那不是领域逻辑。
因此,回答这个问题,不,您不应该注入大量非领域对象/概念,比如查找表和其他基础设施细节。这是一个关注点泄漏到另一个关注点中。领域驱动设计中的工厂和存储库模式最适合将这些关注点与领域模型分开。
但请注意,如果您没有任何领域逻辑,那么您最终会得到贫血的领域对象,即一堆无脑的getter和setter,这就是一些商店声称进行SOA/服务层的方式。
所以,如何兼顾两者的优点呢?如何将你的领域对象专注于领域逻辑,同时将UI、构造、持久化等因素排除在外?我建议您使用像双重分派这样的技术,或者某种形式的限制方法访问
以下是双重分派的示例。假设您有以下代码行:
entity.saveIn(repository);

在您的问题中,saveIn() 将拥有关于数据层的各种知识。使用双重分派,saveIn() 实现了以下功能:
repository.saveEntity(this.foo, this.bar, this.baz);

而仓库的saveEntity()方法拥有保存数据层信息的全部知识,这正是应该的。

除了这种设置,你还可以拥有:

repository.save(entity);

这只是调用

entity.saveIn(this);

我重新阅读了这篇文章,发现实体仍然很薄,因为它只是将其持久性分派给存储库。但在这种情况下,实体应该很薄,因为您没有描述任何其他领域逻辑。在这种情况下,您可以说“放弃双重调度,给我访问器”。
是的,您可以这样做,但在我看来,它会暴露出您的实体实现方式太多,而且那些访问器会分散领域逻辑的注意力。我认为唯一应该有gets和sets的类是以“Accessor”结尾的类。
我很快就会结束这个话题了。就我个人而言,我不会使用saveIn()方法编写我的实体,因为我认为即使只有一个saveIn()方法,也会使领域对象杂乱无章。我使用友元类模式、包私有访问或可能是Builder模式
好了,我说完了。正如我所说,我已经对这个主题着迷了相当长时间。

2
简而言之,您建议从域访问存储库? - aaimnr

1

我同意DeadBeef的观点-其中存在着紧张关系。 然而,我并不真正看出一个域模型仅因为它不能自己保存而被称为“贫血”。

一定还有更多的原因。 例如它之所以贫血是因为服务正在执行所有业务规则而不是域实体。

Service(IRepository) injected

Save(){

DomainEntity.DoSomething();
Repository.Save(DomainEntity);

}

'Do Something' is the business logic of the domain entity.

**This would be anemic**:
Service(IRepository) injected

Save(){

if(DomainEntity.IsSomething)
  DomainEntity.SetItProperty();
Repository.Save(DomainEntity);

}

看到继承的区别了吗?我是看到了 :)


1
“将薄模型转换为服务层对象”是当你真正想编写服务层时所做的事情。
ORM 是当你不想编写服务层时所做的事情。
当你使用 ORM 时,你仍然意识到导航可能涉及查询,但你不会过多考虑它。
查找表可以成为关系上的支撑,当没有非常完整的对象模型时就会被使用。代替物件引用物件,你有了代码,必须进行查找。在许多情况下,这些代码退化为仅具有数据库键的静态字符串池。相关方法最终出现在软件中的奇怪位置。
然而,如果有更完整的对象模型,我们就有了一流的“事物”,而不是这些退化的查找值。
例如,我有一些业务交易,其中有一个不同的“费率计划”之一 - 一种定价模型。现在,传统的关系型数据库将费率计划作为一个查找表,其中包含一个代码、一些定价数字和(有时)一个描述。
[每个人都知道这些代码 - 这些代码是神圣的。没有人确定应该是什么正确的描述。但他们知道这些代码。]

但实际上,“费率计划”是与合同相关联的对象;费率计划具有计算最终价格的方法。当应用程序请求合同价格时,合同将一些定价工作委托给关联的费率计划对象。

在生成合同价格时可能进行了一些数据库查询来查找费率计划,但这与两个类之间的责任委派无关。


0
尝试使用“仓储模式”和“领域驱动设计”。DDD建议将某些实体定义为其他对象的聚合根。每个聚合都被封装。这些实体是“持久性无知”的。所有与持久性相关的代码都放在一个仓储对象中,该对象管理实体的数据访问。这样一来,您就不必将持久性相关的代码与业务逻辑混合在一起。如果您对DDD感兴趣,请查看Eric Evans的书。

1
它并没有回答问题。 问题是“应该将存储库注入到领域中吗”? 如果这样做,它会破坏关注点分离。另一方面,如果这些操作由服务执行,可能会导致贫血领域模型。 - aaimnr

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