RDM与ADM(再次)或哪里有中间立场?

时间:2013-05-16 15:21:33

标签: oop domain-driven-design domain-model anemic-domain-model

在网上(和书中)花了无数个小时试图得出关于这个主题的结论,看看很多人 我已经决定发布一些关键问题,我希望有些人比我聪明的人会回答:)

我阅读了Martin Fowler关于ADM(他称之为贫血领域模型)以及书籍的文章,我了解Eric Evan的DDD(领域驱动设计)。他们非常可敬, 经验丰富的建筑师,他们做了一项非凡的工作,将所有这些知识汇编到他们的书籍和文章中(但我知道这几乎是不可能的,因为它 就像在所有印刷媒体中一样)他们的例子通常非常清晰,可以解释一个概念,但不幸的是很难将它们用于现实生活中。

在这里,我将快速解释一些我对你的选择非常感兴趣的案例(RDM / ADM + TS(服务)),让我们假设一个IoC容器正在接线(虽然它几乎不相关):

案例I / 1:

任务:下订单

ADM + TS:

要求:
订购项目 - 数据(设置+获取)(类似DTO"数据包")
OrderService - 操作(对实体对象的类TS操作)

RDM:

要求:
订单 - 数据和功能(Rich)

案例I / 2:

任务:下订单并在其后发送电子邮件

ADM + TS

要求:
订购项目 - 数据(设置+获取)(类似DTO"数据包")
OrderService - 操作(对实体对象的类TS操作)
EmailService - 发送电子邮件
(可选)OrderServiceEmailDecorator - 分离实际下订单和发送电子邮件的问题

注释:
解决方案:
一个。使用现有的OrderService并向其添加电子邮件,在这种情况下,OrderService依赖于EmailService
湾将问题分成一个装饰器,我们可以将其与IoC中的服务连接在一起,如果我们想要这个,可以根据需要使用

RDM:

要求:
订单 - 数据和功能(Rich)
? EmailService - 发送电子邮件
? EventHandler - 捕捉事件

注释:
那么,在这种情况下,人们通常建议以下事项:
一个。 "将您的依赖项注入域层":这将使域层非常繁重,并且在所有地方都充满了依赖性。
湾"将服务与地点(...)呼叫一起传递":随着越来越多的家属进入,这将使功能签名一直在变化。
C。 "提出一项重要操作已完成的事件":即使是最强大的RDM倡导者也表示持久性不应该直接在域模型中,这意味着我们是 在这里举起一个事件,但是操作没有完全执行(持续)。所以我们可能会在完成之前发送电子邮件。我们可以说电子邮件可能会失败,所以它不是完美的, 但我认为实际下订单是这里的主要操作,发送电子邮件只是一个通知,而且,可以重复,加上你的屏幕通知等。 你明白这一点,实际上,下订单并不依赖于能够发送通知电子邮件,但如果持续下订单失败,你绝对不想发送电子邮件。 但有些人可以说,在存储库中引发类似的事件,这些事件会被持久化,更像是它,但是会传播这些事件。

案例I / 3:

任务:订单可以批量放置,我们只想发送一封包含所有订单的通知电子邮件(请不要开始评论这些只是同一订单上的商品, 这是一个例子。)

ADM + TS

要求:
订购项目 - 数据(设置+获取)(类似DTO"数据包")
OrderService - 操作(对实体对象的类TS操作)
BulkOrderService - 将依赖于OrderService(非装饰)
EmailService - 发送电子邮件
BulkOrderServiceEmailDecorator - 依赖EmailService发送聚合的电子邮件

注释:
我们使用(装饰)BulkOrderService

而不是使用装饰的OrderService

RDM:

要求:
订单 - 数据和功能(Rich)
? EmailService - 发送电子邮件
? EventHandler - 捕捉事件

注释: 我们的域对象现在变得有点复杂,我们不能在它上放一个.bulkPlace(),因为显然我想在同一个上下文中放置多个命令,因为逻辑必须存在一层, 让我们在控制器级别说,并在每个订单上调用每个地方(),在这种情况下: 从上面继续(关于I / 2 RDM解决方案的承诺):
一个。 ("注入的家属")我们如何绕过这里发送的电子邮件?现在我们需要一个.placeWithEmailAndAnyOtherDependency ..和一个.placeWithout ......? 你不能完全装饰域对象是公平的 湾("传递服务")现在你可以这样做,如果你传递null而不是它不会发送电子邮件的服务(但这看起来很狡猾)
C。 ("提升活动")这是一个问题,现在我们将电子邮件发送到此事件,我们想重复使用.place()调用,即使在批量订单上也会发送多封电子邮件, 除非我们能以某种方式分离它(也不能真正装饰存储库)

现在其中一些问题可能可以用AOP而不是装饰器解决,但仍然感觉很乱。

案例I / 4:

任务:现在我们有多个入口点,因为我们希望能够安排"安排"来自我们的调度程序的定期批量订单, 但也想直接在我们的网站上保留此功能。 (或者我可以说我们蚂蚁有一个控制台客户端以及网络客户端,无论如何,点我们的网络控制器赢得了这项工作, 不是直接反正)

ADM + TS

要求(不变):
订购项目 - 数据(设置+获取)(类似DTO"数据包")
OrderService - 操作(对实体对象的类TS操作)
BulkOrderService - 将依赖于OrderService(非装饰)
EmailService - 发送电子邮件
BulkOrderServiceEmailDecorator - 依赖EmailService发送聚合的电子邮件

注释: 我们不使用装饰的OrderService,而是使用(装饰)BulkOrderService - 基本上没有任何更改

RDM:

要求:
订单 - 数据和功能(Rich)
? EmailService - 发送电子邮件
? EventHandler - 捕捉事件

注释: 取决于我们在I / 2中做了什么,I / 3仍然适用的相同问题,但除此之外,我们不能再使用我们的控制器循环通过订单,或者如果我们这样做 进入类似TS的东西,我们又回到了与ADM + TS相似的分层架构

所以,我的主要问题是我无法在RDM中找到一个明确的,合适的解决方案来解决这样一个简单的问题,即使在阅读和google-ing人们推荐不同的东西之后, 这对于解决一件事很有好处,但是从另一件事中流血,而ADM + TS解决方案在处理它们时感觉更灵活。 (更不用说你不需要DTO-s, 因为您的ADM基本上是您的DTO,您可以将事件传递给视图 - 所以不需要转换)

如果您对如何以适合的方式处理(逐步)使用RDM的案例I / 2和/ 3有意见,请发表评论,但如果您这样做,请提供 并回答所有问题(所有4个,或至少最后3个,因为1不是真正的问题)!不只是你有一个方便的答案(如一半的任务/等)

由于

更新 看到一些答案,我可能应该选择一个不同的"实体"然后着名的这个运动的命令(我只是想选择一个熟悉的)。无论如何,作为一个补充,试着想象Case I / 2,I / 3,I / 4不是初始要求,它们是有机发展的。这些要求是逐步添加的。所以首先你被告知每当有订单时都会发送一封电子邮件,现在如果你以任何方式将它们连接在一起,那么当我/ 3点击批量订单时你会遇到问题。即使您只是将电子邮件发送到消息总线而且它还没有发送,您在批量上做了什么?然后你把消息放在总线上然后删除它/做清理?或者基于I / 2的任何其他操作应该被触发,但是基于I / 3,不再适用,只需要执行它们然后还原它们?这听起来不对

2 个答案:

答案 0 :(得分:0)

  

案例I / 2

要解决过早发送电子邮件以应对失败的域事件,请查看here

  

案例1/3

如果批量订单的放置是一个用例,那么在域中将其显式化。无论您使用域模型还是事务脚本,您仍然拥有应用程序服务。反过来,此应用程序服务将具有实现批量订单放置用例的方法。就域模型而言,您可以创建一个BulkOrder聚合,使用该聚合可以选择性地关联现有订单。

总的来说,我认为您对富域模型的想法过于严格。即使使用丰富的域模型,您仍然可以协调应用程序服务。此外,通过强制隐式概念变得明确,对域模型的重构通常可以深入了解域本身。

答案 1 :(得分:0)

订单始终是指定客户订购的产品和数量的文档。由于这个原因,它是不可改变的,所以我们不能谈论过多的富有行为。但重要的是订单是一个商业概念,它不需要有20种方法。对于价格,总额和税收,有一个附加到订单的发票的业务概念。

案例I / 1,I / 2

我没有看到使用域事件的问题。假设订单已提交

 class ManageOrderHandler:IExecute<CreateOrder>
  {
   public void Execute(CreateOrder cmd)
   {
         Order order=someFactory.CreateOrder(cmd); //not important
         _repository.Save(order);
         _bus.Publish(new OrderSubmitted());
    }
  }

在订单被保留后,该事件仅发布

 class Notifier:ISubscribeTo<OrderSubmitted>
 {
       NotifyService _service;//constructor injected

        public void Handle(OrderSubmitted evnt)
        {
             _service.Notify(/* relevant parameters */);
        }
  }     

  class NotifyService
  {
        public NotifyService(ISendEmails emailNotifier, ISendSms smsNotifier /* etc */)
        {}

        public void Notify(/* arguments */)
        {
             // depending on settings and the arguments send email and/or sms and/or others
        }
  }

当然,NotifyService(可怜的命名)可以直接作为OrderSubmitted的处理程序,但是现在我更喜欢把事情解耦为可能。

案例I / 3,I / 4

有点相同,但经过调整。存储库接受多个订单,因此所有订单都将被视为交易。已发布的事件将是包含相关数据的BulkOrdersSubmitted。 NotifyService未更改。

公平地说,我更担心modeling the Domain correctly。使用消息驱动的架构可以让你编写解耦的东西,并且更容易调整或实现​​新功能。但为此你真的需要让Domain建模正确。