贫血领域模型:优点/缺点


Answers:


39

优点:

  • 您可以声称这是一个域模型,向您的开发人员朋友吹牛,然后将其放在简历中。
  • 从数据库表自动生成很容易。
  • 它惊人地映射到数据传输对象。

缺点:

  • 您的域逻辑存在于其他地方,可能在充满类(静态)方法的类中。或您的GUI代码。或在多个地方,所有逻辑都有冲突。
  • 这是一种反模式,因此其他开发人员会询问您是否了解面向对象设计的概念。

7
+1关于从数据库表生成代码的观点对于某些人而言非常重要。如果您处于快速原型开发模式,那么自动代码生成将非常有帮助。我们不假装这是适当的面向对象设计,而将它们放在一起会产生“技术负担”。但是,在许多项目中,上市时间是重中之重。
比尔·卡温

62
否。贫血模型是否是反模式是一个见解。福勒(我尊重并且通常会从事的工作)说是的。我不同意(并不是说我的话没有任何意义),并且面向对象的许多人也不同意。整个行业从经验中了解到,纯OO建模并非通常适用于所有情况。此外,贫血模型仍可以遵守OO建模准则。因此,看到适用于特定情况的情况不会使人们对OO设计的理解产生疑问。
luis.espinal 2011年

12
是否有任何模式是反模式是有待商opinion的。健壮的领域模型是好的OO设计,贫血的领域模型是不好的OO设计。因此,在OO中,这是一种反模式。这并不意味着它不会在所有情况下都被使用或不适当,但是从个人经验来看,我认为它比单身成瘾更糟。
特里·威尔科克斯

7
我觉得函数式编程有一种趋势,领域模型往往会产生很多副作用。我感到贫乏的领域模型正在卷土重来,因为它允许一种更具功能性的编程风格,您的业务逻辑期望数据输入并返回处理后的数据。您的服务层通过知道需要使用哪种业务逻辑方法处理哪些数据来监督所有流程。
Didier A.

2
@TerryWilcox好吧,我不是函数专家,但是函数编程也具有数据结构。贫血领域模型使用对象就像数据容器一样,它们更像是结构。我的观点是,使用ADM,您可以朝着一种更实用的方式迈进,在这种方式下,您将拥有不可变的数据,这些数据随着时间的推移会转换为越来越新的状态。例如,我可以看到它在Scala中完成,尝试利用一些OOP并对其应用一些FP。有点像他们在这里解释的那样:slideshare.net/debasishg/qconny-12
Didier A.

131

当“ Anemic Domain Model”成为反模式时,为什么有这么多系统实现此功能?

我认为有几个原因

1.系统的复杂性

如果要实现一个简单的系统(几乎是您在Internet上找到的所有示例和示例代码),请执行以下操作:

将产品添加到订单

我把这个功能放在订单上

public void Order.AddOrderLine(Product product)
{
    OrderLines.Add(new OrderLine(product));
}

尼斯和超级面向对象。

现在让我们说,我需要确保需要验证产品是否存在于库存中,如果不存在则抛出异常。

我真的不能再将其放入“订单”了,因为我不希望我的订单依赖于“库存”,因此现在需要继续使用该服务

public void OrderService.AddOrderLine(Order order, Product product)
{
    if (!InventoryService.Has(product)
       throw new AddProductException

    order.AddOrderLine(product);
}

我也可以将IInventoryService传递给Order.AddOrderLine,这是另一个选择,但是仍然使Order依赖于InventoryService。

Order.AddOrderLine仍有一些功能,但通常仅限于Order范围,而根据我的经验,Order范围之外还有很多业务逻辑。

当系统不仅仅是基本的CRUD时,您最终将在OrderService中使用大部分逻辑,而在Order中则很少。

2.开发人员对OOP的看法

互联网上有很多关于实体应采用哪种逻辑的激​​烈讨论。

就像是

订单保存

Order应该知道如何自我保存吗?假设我们有相应的存储库。

现在,订单可以添加订单行吗?如果我尝试使用简单的英语来理解它,那也没有任何意义。用户将产品添加到订单,所以我们应该做User.AddOrderLineToOrder()吗?这似乎有点过分了。

怎么样OrderService.AddOrderLine()。现在有点道理了!

我对OOP的理解是,对于封装,您将函数放在需要访问类的内部状态的类上。如果需要访问Order.OrderLines集合,请将Order.AddOrderLine()放在Order上。这样,类的内部状态就不会暴露出来。

3. IoC容器

使用IoC容器的系统通常是完全贫血的。

这是因为您可以测试具有接口的服务/存储库,但是不能(轻松地)测试域对象,除非将接口放在所有接口上。

由于“ IoC”目前被誉为解决所有编程问题的解决方案,因此许多人盲目地遵循它,最终以“贫血领域模型”告终。

4. OOP很难,程序容易

我对此有一点“知识的诅咒”,但是我发现对于拥有DTO和服务的新开发人员而言,它比Rich Domain容易得多。

可能是因为使用Rich Domain,更难知道将逻辑放在哪个类上。什么时候创建新类?使用哪种模式?等等..

使用无状态服务,您只需将其以最接近的名称添加到服务中即可。


我认为这将取决于它的复杂程度。1.如果它简单如1-2个依赖项-我将其保留在Order类中。2.如果复杂度适中-我将创建OrderLines类并使用Order.OrderLines处理Order <-> OrderLines通信以及相关的依赖项。3.但是在高度复杂的情况下-我将使用应用程序服务OrderService来允许大多数外部依赖关系,同时将任何Order模块范围内的逻辑保持在Order中。
Eric P

13
很棒的总结-所有这些原因都是为什么它不是反模式。对OO的狂热奉献是一种反模式。现在,在没有投影的情况下增加并发性,并观察如何使用Fowler风格的OO使自己陷入困境。查看进行大规模并发的公司(例如google),看看他们正在执行多少OO。
DanH 2013年

很好说。使用Grails,我不确定3度会导致贫血。对于1°,实际上,您可以在任何非平凡的应用程序中结束逻辑在服务层中的移动。但是我仍然发现自己使用Grails以领域为中心的方法。这实际上并不好,因为您的域是逻辑的首选位置,但是在需要时将位向上移动一层。
youi

4
我看不到IoC与贫血的相关性。这似乎是一个完全不相关的问题。服务/存储库还是域对象是实现接口都与测试它们无关。相反,您将测试服务/存储库/域对象,并将它们与它们自己实现接口的依赖项分离。而且,无论您是否正确执行此操作,与我看到的IoC都没有任何关联。

2
我要向Grok努力的DDD问题是您的第一点:“当系统更多时,而不仅仅是基本CRUD,您最终将在OrderService中得到大部分逻辑,而在Order中则很少。” 这对我来说很有意义,但是DDD的目的不是用于比这更复杂的系统吗?
丹玲

20

之后,很长一段时间以来我一直在想一个想法。我认为,“ OOP”一词具有的含义并非真正针对它。众所周知,字谜的意思是“面向对象的程序设计”。当然,重点是“面向”一词。它不是“ OMP”,意思是“对象授权编程”。ADM和RDM都是OOP的示例。它们利用对象,属性,方法接口等。但是,在我们选择封装事物的方式上,ADM和RDM之间存在差异。他们是两个不同的东西。说ADM不好是OOP,这不是准确的说法。也许我们需要各种不同级别的封装术语。另外,我从不喜欢反模式一词。通常由对立小组的成员分配给某物。ADM和RDM都是有效的模式,它们简单地具有不同的目标,并且旨在解决不同的业务需求。我们中那些实践DDD的人至少应该对此表示赞赏,并且不要因为抨击选择实施ADM的人而落到其他人的水平。只是我的想法。


16

“这是一种反模式,因此其他开发人员会询问您是否了解面向对象设计的概念。”

“贫血领域模型是一种反模式。反模式没有优点。”

贫血域模型是否是反模式是一个意见问题。马丁·福勒(Martin Fowler)表示确实如此,许多从内而外了解OO的开发人员表示并非如此。将观点陈述为事实很少有帮助。

一个,即使它被普遍认为是一种反模式,它仍有可能(尽管相对较少)有上升空间。


2
... 哪个是?我并不是想欺骗您或任何人,但我真的对专业人士感到好奇。缺点已经被提及了上千次,但是我仍然在寻求ADM的优点(除了争论性的短语)。
struppi

6
...似乎有人试图以一点点表现来扮演恶魔的崇拜。the chances are it would still have some (though relatively little) upside.然后,请命名!至少一个,而不是假设!
雪橇

2
对于与单类无关的任何事物(任何实际业务),更容易知道某些逻辑在哪里。就像在其他地方所说的那样,“富域模型”最终在多个地方(服务层,对象图中涉及的多个类……)具有逻辑。而且,RDM不能让您轻松了解完整的业务逻辑,也无法轻松避免出现循环等。无逻辑的哑数据结构在OO应用程序中占有一席之地。
ymajoros 2013年

1
我觉得这篇文章给出了一个很好的理由说ADM是不是一个反blog.inf.ed.ac.uk/sapm/2014/02/04/...
syclee

1
反模式总是会产生问题,并且我们已经成功交付了一些较大的应用程序(使用10到300KLOC的10位开发人员进行了2到3年的开发),并且使用了高质量的解决方案,并且使用该模式并没有感觉到有问题,因此如果它不那么糟糕允许您在使用时创建良好的代码。
Ignacio Soler Garcia

13

在我看来,福勒的主要异议是从以下意义上说ADM不是OO。如果人们围绕被动数据结构“从头开始”设计一个由其他代码段操纵的系统,那么这肯定会比面向对象设计更像过程设计。

我建议至少有两种力量可以产生这种设计:

  1. 仍然在程序上认为仍需要在面向对象的环境中工作(或假设他们可以...)以产生系统的设计者/程序员,以及

  2. 开发人员致力于在非OO方式(无论语言如何)设计的遗留系统上放置类似服务的“面孔” 。

例如,如果正在构建一组服务以公开现有COBOL大型机应用程序的功能,则可以使用反映内部COBOL数据结构的概念模型来定义服务和接口。但是,如果服务将新模型映射到旧数据以使用现有的但隐藏的实现,那么就福勒的文章而言,新模型可能很“贫瘠”-例如,一组TransferObject样式的定义和没有真实行为的关系。

这种妥协对于理想的纯OO系统必须与现有的非OO环境进行交互的边界非常普遍。


8

如果您的团队无法或不愿构建富域模型(RDM)并随时间进行维护,那么贫血域模型(ADM)可能是一个不错的选择。要赢得RDM,需要仔细注意系统中使用的主要抽象。可以看出,在任何开发组中,只有不超过一半的成员,也许只有十分之一的成员具有抽象能力。除非该干部(也许只有一个开发人员)能够对整个团队的活动保持影响,否则RDM将屈服于熵。

熵RDM尤其会造成伤害。它的开发人员将学习严酷的教训。起初,他们将能够满足利益相关者的期望,因为他们将没有任何历史可言。但是随着他们的系统变得更加复杂(而不是复杂),它将变得脆弱。开发人员将尝试重用代码,但倾向于在开发中引入新的错误或后退(从而超出了他们的估计)。

相反,ADM开发人员对自己的期望较低,因为他们不会期望为新功能重用太多代码。随着时间的流逝,他们将拥有许多不一致的系统,但它可能不会意外中断。与成功的RDM相比,他们的上市时间将更长,但是他们的利益相关者不太可能意识到这种可能性。


5

“开发人员致力于在以非OO方式(无论语言如何)设计的遗留系统上放置类似服务的“面孔”。

如果您想到许多LOB应用程序,那么这些旧系统通常将不会使用与您相同的域模型。Anemic域模型通过在服务类中使用业务逻辑来解决此问题。您可以将所有这些接口代码放入模型中(按照传统的OO含义)-但通常最终会失去模块化。


1
答对了。这是很多人似乎想念船的地方。ADM确实有其目的。他们的滥用是一种反模式,但他们本身不是。
luis.espinal 2011年

4

当我第一次遇到有关Anemic Domain Model的文章时,我以为“这是我的工作。恐怖!” 我坚持并遵循了对埃里克·埃文(Eric Evan)的书的引用,认为这是一个很好的例子,并下载了源代码。事实证明,“不使用贫血域模型”并不意味着“不使用服务类,不使用中介器,不使用策略”,甚至“将逻辑放在要操纵的类上”。

DDD示例具有服务类,XyzUpdaters,单例和IoC。

我仍然对确切的Anemic域模型感到困惑。我希望“我一看到它就会知道”。目前,我对良好设计的积极实例感到满意。


1
随后,我购买了埃里克·埃文(Eric Evan)的书,并强烈推荐它。它提供了与贫血领域模型相反的可靠示例。
杰米2012年

4

在使用带有ADM的“成熟”系统之后,我感到至少可以提供一些有关该问题的轶事反馈。

1)缺乏封装

在带有ADM的实时系统中,可以写例如'obj.x = 100; obj.save”,即使这违反了业务逻辑。如果不变量是在对象上建模的,这会导致许多错误。我认为这些错误的严重性和普遍性对ADM来说是最严重的负面影响。

我觉得在这里指出这一点很重要,这是功能解决方案与ADM的过程解决方案之间显着不同的地方,而其他人可能在ADM和功能解决方案之间的表面相似性所引起的任何相似之处都是偶然的。

2)代码膨胀

我估计ADM中产生的代码量是OOP / RDM解决方案将产生的代码量的5-10倍。这可能是由于50%是代码重复,30%是样板代码以及20%是由于缺少RDM而引起的问题的解决或解决。

3)对领域问题了解不足

ADM和对域问题的理解不一而足。幼稚的解决方案出现了,由于很难用现有的DM支持需求,因此对需求的考虑不充分;鉴于更长的开发时间和缺乏灵活性,ADM成为业务创新的重要障碍。

4)维修困难

考虑到域概念可能不仅仅是复制和粘贴的重新实现,因此需要一定程度的严格性,以确保在域表示的所有位置都对其进行更改。这通常会导致对相同的错误进行多次调查和修复。

5)增加入职难度

我认为RDM的好处之一是概念的凝聚力,可以更快地了解领域。使用ADM时,概念可能分散且缺乏清晰性,因此新开发人员更难获得。

我也很想将ADM的运营支持成本包括在比RDM更高的成本中,但这将取决于许多因素。

正如其他人指出的那样,请查看DDD(Greg Evans,Vince Vaughn和Scott Millett)以了解RDM的好处。


格雷格·埃文斯是格雷格·杨和埃里克·埃文斯的秘密孩子吗?我也不知道文斯·沃恩(Vince Vaughn)也在写DDD书,但是也许沃恩·弗农(Vaughn Vernon)也开始表演了:)
knittl

2

与大多数反模式相同,它的优点是:它可以让很多人长时间忙碌。由于管理者管理更多的人时往往会得到更高的报酬,因此有强烈的动机去不进行改进。


1
不,真实的生活
Stephan Eggermont 2011年

7
在这种情况下,让我改一下。^^^纯粹的轶事证据,用于推论一个通用的命题。
luis.espinal,2011年

当我们发现足够的轶事证据时,我们也许可以得出一个模式。这种模式是众所周知的,并且在有关变更管理的文献中有充分描述。
Stephan Eggermont 2011年

4
“而且这种模式是众所周知的,并且在有关变更管理的文献中有充分描述。” -请引用。更重要的是,相关性并不意味着因果关系。意思是,仅仅因为您看到人们由于就地使用反模式(特别是软件模式,以及贫血的领域模型)而在繁文tape节中转动了轮子,但这并不意味着后者的存在证明了前者的合理性,即你刚刚吹牛什么。修辞口号是对复杂问题的解释的讽刺漫画。同样,相关并不意味着因果关系。
luis.espinal 2011年

4
您可能需要阅读Gerald M. Weinberg的《质量软件管理》,尤其是第4卷。我仅声明可能存在的原因是持续存在,而不是其继续存在。彼得原则提供了一个适当的选择。
Stephan Eggermont 2011年

2

与Eric P的回答以及上面其他一些人的回答一致,似乎ADM的主要缺点是OOD的丢失,特别是将域概念的逻辑和数据保持在一起,从而隐藏实现细节的同时, API可以很丰富。

埃里克(Eric)继续指出,领域类之外通常还存在一些信息,这些信息对于对该类起作用的逻辑是必需的,例如在将商品添加到订单之前检查库存。但是,我想知道答案是服务层是否拥有这种总体逻辑,还是作为对象设计的一部分更好地进行处理。 有人知道库存对象,产品对象和订单对象。也许它只是一个OrderSystem对象,它具有一个库存成员,一个订单列表等。这看起来与Service并没有太大区别,但是我认为它在概念上更加一致。

或这样看:您可以拥有一个具有内部信用余额的User,并且每次调用User.addItemToOrder(item)时,它都会获取商品的价格并在添加信用之前检查其信用,等等。这看起来很合理设计。我不确定用Service.addItemToUserOrder(user,item)替换掉它到底丢失了什么,但是我也不知道获得了什么。我猜想这将是多余的代码层,再加上笨拙的书写风格和底层域模型的强制性无知。


1

应当注意,随着系统的复杂性和变化粒度的增长,精心设计的消息传递对象模型提供的接口点的封装和合并使无需广泛重构即可更安全地更改和维护关键代码。

由ADM创建的服务层虽然确实易于实施(因为它们需要的思考相对较少,并且具有许多分散的接口点),但是当需要修改实时且不断发展的系统时,可能会给您带来麻烦。

我还可以补充一点,并不是所有情况下都根本不需要领域模型(更不用说ADM了)。有时最好使用更具过程性/功能性的任务,该任务是数据驱动的,并且不依赖于应用程序范围的逻辑/业务规则。

如果您要决定整个应用程序的优缺点,我认为在开始编写一行代码之前,首先设计给定应用程序的外观是很重要的。在对两种样式的应用程序进行CRC编码或线框化后,请退后一步,确定哪种方法更有意义并更适合该应用程序。

还要预先考虑哪一个将更容易维护...


1

它具有更好的可预测性。经理们喜欢这样,特别是如果项目有时间和材料。每次更改都意味着很多工作,因此艰巨的工作可以隐藏在很多重复的工作后面。在设计良好的DRY系统中,可预测性非常差,因为您一直在做新事物。


1

在我第一次阅读Eric Evans的关于域驱动设计的书之后,我并没有真正理解,这不仅仅是用于设计好的域模型类的一堆战术模式。

在学习了有关该主题的更多信息并使用了战略模式之后,我终于开始了解到,起初这是对业务问题的深入了解要解决。

并且只有在此之后,您才能决定系统的哪些部分适合于应用战术模式(例如聚合,实体,存储库等)以及所谓的丰富域模型(与贫乏模型相对)。但是为了从这些模式中受益,必须在业务逻辑方面足够的复杂性针对该系统的那部分,在。

因此,在实施眼前问题的解决方案时,应首先确定是否可以通过使用基于CRUD的方法或投资于域模型来更好地解决此特定问题与上述战术模式一起。

如果CRUD更有意义,例如,如果没有复杂的业务逻辑,并且大多数逻辑都与转换,实现域模型的数据的传输和持久化有关,那将是不必要的过大杀伤力。这并不意味着不会有很多工作要做,而仅仅是产生最大工作量的不是业务规则。但是在这种情况下,根本就没有贫血领域模型,仅仅是因为根本没有领域模型。您宁愿看到的是诸如DTO(数据传输对象)或DAO之(数据访问对象)和将对数据进行操作的服务类。而且,相应的操作在很大程度上与将数据从一种表示形式转换为另一种表示形式以及在几乎没有或几乎没有业务逻辑的情况下移动数据有关。

如果您确定有很多复杂的业务逻辑,这些逻辑也会随着时间的推移而发生变化,那么根据我的经验,投资领域模型是一个好主意。原因是更容易通过代码表示业务透视图,并且更容易理解反映业务域及其规则的相应操作。这并不意味着在每个用例中都必须有域模型类。例如,如果没有要被突变和持久化的状态,那么也只能有包含域逻辑的域服务被实现得更像纯函数。

但是,如果在业务领域中也存在具有目的和意义的要变异和持久的状态,则该状态和更改该状态的行为应被封装。这样一来,没有人能绕过容易的业务规则,从而导致无效状态以及严重的故障。所谓的贫血症领域模型通常是此类问题的根源,。如果您看到代码,其中不同的组件在同一个“贫乏”域模型类上运行,则在不关心或不了解该业务实体的整体不变性的情况下,检查其状态的某些部分并更改其状态的某些部分,通常是这种情况。不必将其称为反模式,但重要的是要了解您会失去丰富领域模型的许多好处在基于DDD的方法中,以及上述问题。当使用将行为及其数据放在同一个类中的域模型时,也可以有很多不同的“客户端”调用该类的操作,但是他们不必关心遵守业务实体的业务不变量,因为域模型类将始终处理该问题,并且还可以将无效操作告知“客户端”,甚至将异常作为安全网抛出。

因此,重要的是,我认为重要的是不要将类(如DTO或DAO)之类的数据结构与贫血的领域模型类混为一谈。在精心选择的基于CRUD的方法中,尝试使用域模型没有任何优势,因为业务逻辑太复杂了。

通过贫血领域模型,我将参考代码,从中我可以看到有很多复杂的业务逻辑和业务规则分布在不同的组件上,而这些组件应该更接近这些逻辑所更改的数据。

我在此过程中还吸取了另一课:如果您尝试在代码中使用与牛排店主在日常工作中使用的相同的业务语言(也称为无处不在的语言),则您已经获得了许多有关理解业务领域和提高代码的可读性,无论您使用基于CRUD的方法还是基于域模型的方法,都将对您有很大帮助。


0

为了扩展Michael的答案,我认为(相当)清楚该代码应该去哪里:放入专门的调解员,该调解员处理订单和库存之间的交互。

从我的观点来看,关于域的关键是它必须保持简单的测试行为, isInThisState()方法等。以我的经验,这些也散布在大多数公司的服务眼泪中(sic :)),并且要么被复制,要么被无休止地重写。所有这些都违反了标准的融合规则。

在我看来,这种方法应该是旨在用于保存许多BIZ beahaviour作为为实用一个DM,把休息,在明确指定的区域(即不在服务)


关于调解员的事情是,它与过程编程之间几乎没有什么区别。您只是将一些不属于任何一个对象的功能分组。将它们分组在另一个对象中的事实并没有多大区别。
罗伯·格兰特

0

我的团队个人更喜欢ADM。我们有一组业务对象,它们代表域的特定部分。我们使用服务将这些对象保存到数据库。我们的业务对象确实具有方法,但是这些方法只能操纵其内部状态。

通过将对象持久保存到数据库,可以看出使用RDM上的ADM给我们带来的好处。使用我们的旧代码系统的开发人员可以使用我们的业务对象(来自新系统),并继续使用其当前的数据访问层将这些对象持久保存到数据库中。使用RDM将迫使我们的旧系统的开发人员将存储库对象注入到我们的业务模型中……这将与他们当前的数据访问层不一致。


-15

一个贫血域模型是一个反模式。反模式没有优点。


10
我不同意。大多数反模式都有一些极端的情况,在这种情况下,它们比其他替代方法更好。
比尔·卡温

19
反模式确实有优点。这就是为什么人们使用它们的原因,即使它们通常是坏主意。
克里斯托弗·约翰逊

6
当仅是一种意见时(即使是Fowler的才干),将某事模仿为事实是反事实,那不是有效的回答形式。
luis.espinal 2011年

@luis:在我看到您发表评论之前我正要写的内容。另外,存在用于实现目的的工具。如果Fowler不想将其称为OO,那很好。但这并不能自动表明解决方案/体系结构等不佳。
JensG
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.