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

我想知道使用贫血区域模型的优点和缺点(请参阅下面的链接)。

福勒文章

优点:

  • 你可以声称这是一个领域模型,吹嘘你的开发者朋友,并把它放在你的简历。
  • 从数据库表中自动生成很容易。
  • 它非常好地映射到数据传输对象。

缺点:

  • 您的域逻辑存在于其他地方,可能是在类(静态)方法中。 或者你的GUI代码。 或者在多个地方,都有相互冲突的逻辑。
  • 这是一个反模式,所以其他开发人员会问你是否理解面向对象devise的概念。

“贫困领域模型”是反模式,为什么有这么多的系统来实现呢?

我觉得有几个原因

1.系统的复杂性

在一个简单的系统(这几乎是所有的例子和示例代码,你可以在互联网上find),如果我想实现:

将产品添加到订单

我把这个function放在订单上

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

好的和超级面向对象的。

现在让我们来说说,我需要确保我需要validation产品是否存在于库存中,如果不存在则抛出exception。

我不能再把订单放在订单上,因为我不希望我的订单依赖于库存,所以现在需要继续服务

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

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

Order.AddOrderLine中仍然有一些function,但通常它仅限于Order范围,而根据我的经验,更多的Business Logic不在Order范围内。

当系统不仅仅是基本的CRUD时,你将在OrderService中获得大部分的逻辑,而在Order中则很less。

2.开发人员对OOP的看法

互联网上有很多关于逻辑应该放在实体上的讨论。

就像是

Order.Save

订单应该知道如何保存自己? 比方说,我们有这样的仓库。

现在可以订购添加订单行吗? 如果我试图用简单的英语来理解它,那也没有什么意义。 用户将产品添加到订单,所以我们应该做User.AddOrderLineToOrder()? 这似乎是矫枉过正。

如何OrderService.AddOrderLine()。 现在它有道理!

我对OOP的理解是,对于封装,你把函数放在需要访问类的内部状态的类上。 如果我需要访问Order.OrderLines集合,我把订单Order.AddOrderLine()。 这种方式的内部状态不会暴露。

3. IoC容器

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

这是因为您可以testing具有接口的服务/存储库,但不能testing域对象(很容易),除非将接口放在所有接口上。

由于“IoC”目前被称为所有编程问题的解决scheme,因此许多人盲目地遵循它,最终导致无效域模型。

4.面向对象很难,程序很简单

我对这个有一些“ 知识的诅咒 ”,但是我发现对于拥有DTO和服务的新开发者来说,要比Rich Domain容易得多。

可能是因为在Rich Domain中,知道在哪个类上放置逻辑更加困难。 何时创build新的类? 使用哪种模式? 等等..

有了无状态的服务,你只需要用最接近的名字来使用它。

在此之后,我现在头脑中已经有很长的一段时间了。 我相信“OOP”一词的含义并不是真正意义上的。 这个字谜意味着“面向对象的程序devise”,正如我们所熟知的。 当然关注的是“面向”这个词。 这不是“OMP”,意思是“对象强制编程”。 ADM和RDM都是OOP的例子。 他们利用对象,属性,方法接口等等。 然而,ADM和RDM在我们select封装事物方面存在差异。 他们是两回事。 说ADM是不好的OOP不是一个准确的说法。 也许我们需要不同的术语来代替各种各样的封装。 另外,我从来不喜欢反模式这个词。 它通常是由一个对立的小组的成员分配的东西。 ADM和RDM都是有效的模式,它们简单地具有不同的目标,旨在解决不同的业务需求。 我们这些练习DDD的人至less应该欣赏这一点,而不是抨击那些select执行ADM的人。 只是我的想法。

“这是一种反模式,所以其他开发人员会问你是否理解面向对象devise的概念。”

“贫血领域模型是反模式,反模式没有优点。

贫血域模型是否是反模式是一个意见问题。 马丁·福勒说,是的,一些知道面向对象的开发者说,事实并非如此。 作为事实陈述意见是很less有帮助的。

即使被普遍接受为反模式,其机会仍然会有一些(虽然相对较less)的好处。

在我看来,福勒的主要反对意见是ADM在下面的意义上不是OO。 如果从被动数据结构周围devise一个系统,从而被其他代码所操纵,那么这肯定会比面向对象devise更像程序devise。

我build议至less有两种力量可以产生这种devise:

  1. devise师/程序员仍然认为程序上需要在面向对象的环境中工作(或者假设他们可以…)来创build一个新的系统,

  2. 开发人员正在努力将一个类似于服务的“面孔”置于以非OO方式(不pipe语言)devise的遗留系统上。

例如,如果有人正在构build一组服务以公开现有的COBOL大型机应用程序的function,则可以按照反映内部COBOL数据结构的概念模型来定义服务和接口。 但是,如果服务将新模型映射到遗留数据以使用现有隐藏的实现,那么在Fowler的文章中,新模型可能非常“贫乏” – 例如一组TransferObject样式的定义和没有真正的行为的关系。

这种妥协可能在理想纯粹的面向对象系统必须与现有的非面向对象环境进行交互的边界上是很普遍的。

如果您的团队无法或不愿意构build丰富的域模型(RDM)并保持一段时间,则贫血域模型(ADM)可能是一个不错的select。 用RDM获胜需要仔细注意系统中使用的主要抽象。 数字显示,在任何一个开发小组中,不超过一半的成员可能只有十分之一的成员具有抽象能力。 除非这个干部(也许只有一个开发人员)能够对整个集团的活动保持影响,否则RDM将屈服于熵。

而熵的RDM以特别的方式受到伤害。 其开发者将学习严酷的课程。 起初他们将能够满足其利益相关者的期望,因为他们将没有历史辜负。 但随着他们的系统变得更复杂(不复杂) ,它将变得脆弱; 开发人员将尝试重用代码,但往往会引发新的错误或开发中的回溯(从而超出他们的估计)。

相比之下,ADM开发人员对自己的期望会降低,因为他们不希望重复使用尽可能多的新function代码。 随着时间的推移,他们将会有一个很多不一致的系统,但是这个系统可能不会被意外地打破。 他们的上市时间将比成功的RDM更长,但他们的利益相关者不可能认识到这种可能性。

“开发人员正在努力在一个以非OO方式(不pipe语言)devise的遗留系统上提供类似服务的”面子“。”

如果您想到许多LOB应用程序,那么这些传统系统通常不会像您那样使用相同的域模型。 贫血域模型通过在服务类中使用业务逻辑来解决这个问题。 你可以把所有的接口代码放在你的模型里面(在传统的OO意义上说),但是你最终会失去模块化。

当我第一次遇到“贫困领域模型”这篇文章时,我以为是“神圣的,这就是我所做的,恐怖! 我坚持不懈地遵循埃里克·埃文(Eric Evan)的书中提到的一个很好的例子,并下载了源代码。 事实certificate,“不使用贫血领域模型”并不意味着“不使用服务类,不使用中介,不使用策略”,甚至“把逻辑放在被操纵的类上”。

DDD的例子有服务类,XyzUpdaters,单身人士和IoC。

我仍然困惑于一个贫血域模型是什么。 我期望“当我看到它时我会知道的”。 现在我满足于一个好devise的正面例子。

和大多数反模式一样,它可以让你在很长一段时间内保持很多人的忙碌。 由于pipe理者在pipe理更多人时倾向于获得更多报酬,因此有很强的动力不去改善。

根据Eric P的回答以及上面其他人的回答,似乎ADM的主要缺点是OOD的丢失,特别是将域概念的逻辑和数据保存在一起,以便实现细节隐藏该API可以是丰富的。

Eric接着指出,域类之外的信息往往是在该类上作用的逻辑所必需的,比如在将订单添加到订单之前检查库存。 不过,我的问题是,答案是否是一个包含这个总体逻辑的服务层,还是作为对象devise的一部分来处理呢? 有人必须知道清单对象,产品对象和订单对象。 也许它只是一个OrderSystem对象,它有一个Inventory成员,一个Orders列表等等。 这与服务看起来不太一样,但我认为它在概念上更加连贯。

或者用这种方式来看待:您可以让用户拥有内部贷方余额,并且每次调用User.addItemToOrder(item)时,都会获取该项目的价格,并在添加之前检查信用额度等等。这似乎是一个合理的OOdevise。 我不能确定是什么因为用Service.addItemToUserOrder(user,item)来代替而丢失的,但是我不知道得到了什么。 我想,损失将会是额外的代码层,再加上写作的沉闷风格以及底层域模型的强制性无知。

与ADM合作的“成熟”系统,我觉得至less可以提供一些关于这个问题的轶事反馈。

1)缺less封装

在带有ADM的现场系统中,可以写出例如'obj.x = 100; obj.save“,即使这违反了商业逻辑。 这会导致一些错误,如果不variables是在对象上build模的话就不会遇到。 我认为这些错误的严重性和普遍性是对ADM最严重的负面影响。

我觉得在这里指出这一点很重要,这就是ADM的function性解决scheme和程序性解决scheme差异很大,而其他人可能已经在ADM和function性解决scheme之间的表面相似性方面有任何相似之处是偶然的。

2)代码膨胀

我估计ADM生成的代码量是OOP / RDM解决scheme创build的5-10倍。 这大概是代码重复的50%,30%是锅炉代码,20%是解决或解决由于缺乏RDM而引起的问题。

3)对领域问题的理解不够

ADM和对领域问题的理解不太一致。 天真的解决scheme出现了,由于现有DM的支持困难,所以要求考虑不周,由于开发时间较长,缺乏灵活性,ADM成为业务创新的重要障碍。

4)难以维修

需要一定程度的严格性来确保域概念在所有expression的地方都被改变,因为这个概念可能不仅仅是复制和粘贴重新实现。 这经常会导致同样的错误被多次调查和修复。

5)join难度增加

我认为RDM的好处之一就是可以更快地理解域的概念的凝聚力。 由于ADM概念可能分散且缺乏清晰度,因此新开发人员难以获得。

我也想把ADM的运营支持成本包括在RDM中,但是这要取决于许多因素。

正如其他人所指出的,看看DDD(Greg Evans,Vince Vaughn和Scott Millett)RDM的好处。

为了扩展Michael的答案,我认为它已经(相当)清楚了代码应该到哪里:进入一个专门的Mediator,处理Order和Inventory之间的交互。

从我的POV来看,关键领域的关键是它必须保存简单的testing行为isInThisState()等方法。在我的经​​验中,这些也分散在整个服务眼泪(原文:))在大多数公司,并要么无休止地复制重写。 所有这些都打破了标准的凝聚力规则。

在我看来,这样做的方法应该是争取一个DM,尽可能多地把握商业行为,把其余部分放在明确指定的地方(即不在服务范围内)

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

我们在使用ADM over RDM方面的好处可以从我们如何将对象持久化到db中看出来。 使用我们的遗留代码系统的开发人员可以使用我们的业务对象(来自新系统),并继续使用他们当前的数据访问层将这些对象保存到数据库。 使用RDM将迫使我们的遗留系统的开发人员将Repository对象注入到我们的业务模型中,这与他们当前的数据访问层不一致。

值得注意的是,随着系统的复杂性和变化的粒度的增长,由devise良好的消息传递对象模型提供的接口点的封装和合并使得在没有广泛的重构的情况下改变和维护关键代码变得更加安全。

由ADM创build的服务层虽然当然更容易实现(因为他们只需要相对较less的思想,并且拥有许多分散的接口点),在修改现有的不断增长的系统时,可能会造成麻烦。

我还可以补充说,并不是所有的情况都要求域模型(更不用说ADM模块)。 有时最好使用更多的过程/function风格的任务是数据驱动的,不依赖于应用程序范围的逻辑/业务规则。

如果您正在尝试为整个应用程序决定优缺点,那么我认为首先应该为您的应用程序devise每个应用程序的样子,这一点很重要,除此之外您甚至可以开始编写一行代码。 一旦你对这两种风格的应用程序进行了CRC校验或线框化,退一步,决定哪一个更合理,更好地适应应用程序。

另外想一想,哪一个更容易维护…

它提供了更好的可预测性。 这样的经理人,特别是如果项目是有时间和材料的。 每一次改变都意味着很多的工作,所以很多重复的工作都可以隐藏起来。 在一个devise良好的DRY系统中,可预测性非常差,因为你不断地做新事物。

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