1

在网上(和书本)上花费了无数小时试图得出关于这个主题的结论,查看了许多人的观点,以及试图权衡利弊的不同方面之后,我决定发布一些我认为的关键问题希望比我聪明的人回答:)

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

在这里,我将快速解释一些我对您在 (RDM / ADM+TS(Service)) 中的选择非常感兴趣的案例,让我们假设一个 IoC 容器正在执行布线(尽管它大多无关紧要):

案例一/1:

任务:下订单

ADM+TS:

要求:
Order oject - 数据(set+get)(类似 DTO 的“数据包”)
OrderService - 操作(对实体对象的类似 TS 的操作)

RDM:

要求:
订单对象 - 数据和功能(丰富)

案例一/二:

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

ADM+TS

要求:
Order oject - 数据(set+get)(类似 DTO 的“数据包”)
OrderService - 操作(对实体对象的类似 TS 的操作)
EmailService - 发送电子邮件
(可选) OrderServiceEmailDecorator - 分离实际放置的关注点订单,并发送电子邮件

评论:
解决方案:
a。使用现有的 OrderService 并向其添加电子邮件,在这种情况下,OrderService 依赖于 EmailService
b。将关注点分离到一个装饰器中,我们可以将其与 IoC 中的服务连接在一起,并根据我们是否需要按需使用

RDM:

要求:
订购项目 - 数据和功能(丰富)
?EmailService - 发送电子邮件
?EventHandler - 捕捉事件

评论:
嗯,在这种情况下,人们通常会推荐以下内容
:“将您的依赖项注入域层”:这将使域层非常沉重,并且到处都是依赖项。
湾。“将服务与 place(...) 调用一起传递”:随着越来越多的依赖项进入它,这将使函数签名一直发生变化。
C。“引发一个重要操作已完成的事件”:即使是最强大的 RDM 倡导者也说持久性不应该直接存在于域模型中,这意味着我们在这里引发了一个事件,但是该操作并未完全执行(持久化)。所以我们可能会在完成之前发送一封电子邮件。我们可以说电子邮件可能会失败,所以它不是完美的,但我认为实际上下订单是这里的主要操作,发送电子邮件只是一个通知,而且,可以重复,加上你在屏幕上通知等。你明白了,真的,下订单并不依赖于能够发送通知电子邮件,但如果坚持订单失败,你肯定不想发送电子邮件。但是有些人可以说,在它被持久化的存储库中引发类似的事件,

案例一/3:

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

ADM+TS

要求:
Order oject - 数据(set+get)(类似 DTO 的“数据包”)
OrderService - 操作(对实体对象的类似 TS 的操作)
BulkOrderService - 将取决于 OrderService(非修饰)
EmailService - 发送电子邮件
BulkOrderServiceEmailDecorator -依赖 EmailService 发送聚合邮件

评论:
我们不使用装饰的 OrderService,而是使用(装饰的)BulkOrderService

RDM:

要求:
订购项目 - 数据和功能(丰富)
?EmailService - 发送电子邮件
?EventHandler - 捕捉事件

评论:我们的域对象现在变得有点复杂了,我们不能在上面放一个 .bulkPlace() 因为显然我想在同一个上下文中放置多个订单,所以逻辑必须存在一层,比如说在控制器级别,并在每个订单上调用每个 place(),在这种情况下:从上面继续(基于 I/2 RDM 解决方案)
:(“注入的依赖项”)我们如何绕过这里发送的电子邮件?现在我们需要一个 .placeWithEmailAndAnyOtherDependency.. 和一个 .placeWithout...?您不能将域对象完全装饰为公平
b。(“通过服务”)现在,如果你通过 null 而不是它不会发送电子邮件的服务,你也许可以这样做(但这看起来很狡猾)
C。(“引发事件”)这是一个问题,现在我们将电子邮件发送绑定到此事件,并且我们希望重用 .place() 调用,即使在批量订单中也会发送多封电子邮件,除非我们可以以某种方式分离它(也不能真正装饰存储库)

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

案例 I/4:

任务:现在我们有多个入口点,因为我们希望能够从我们的调度程序“调度”重复的批量订单,但也希望直接在我们的网站上保留此功能。(或者我可以说我们希望拥有一个控制台客户端以及 Web 客户端,关键是我们的 Web 控制器不会完成这项工作,无论如何都不会直接完成)

ADM+TS

要求(未更改):
订单对象 - 数据(set+get)(类似 DTO 的“数据包”)
OrderService - 操作(对实体对象的类似 TS 的操作)
BulkOrderService - 将取决于 OrderService(未修饰)
EmailService - 发送电子邮件
BulkOrderServiceEmailDecorator - 依赖 EmailService 发送聚合电子邮件

评论:我们没有使用装饰的 OrderService,而是使用(装饰的)BulkOrderService - 基本上没有任何变化

RDM:

要求:
订购项目 - 数据和功能(丰富)
?EmailService - 发送电子邮件
?EventHandler - 捕捉事件

评论:取决于我们在 I/2 中所做的,与 I/3 相同的问题仍然适用,但最重要的是,我们不能再使用我们的控制器来循环命令,或者如果我们这样做,它会变成类似的东西一个 TS,我们又回到了与 ADM+TS 类似的分层架构

所以,我的主要问题是我自己在 RDM 中找不到一个明确的、非常合适的解决方案来解决像这样的简单问题,即使在阅读和谷歌搜索之后人们推荐了不同的东西,这些东西可以很好地解决一件事,但是出血另一个,而 ADM+TS 解决方案在处理它们时感觉更灵活。(更不用说您不需要 DTO-s,因为您的 ADM 本质上就是您的 DTO,您可以将事件传递给视图 - 因此无需进行任何转换)

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

谢谢

更新: 看到一些答案,我可能应该选择一个不同的“实体”,然后是这个练习的著名命令(我只是想选择一个熟悉的)。无论如何,作为补充,试着想象案例 I/2、I/3、I/4 不是初始要求,它们是有机地进化的。这些要求是逐步添加的。所以首先你被告知只要有订单就发送一封电子邮件,现在如果你以任何方式将它们结合在一起,当 I/3 与批量订单命中时你会遇到问题。即使您只是将电子邮件放在消息总线上,而它还没有发送,您会如何批量处理?然后您将消息放在总线上然后将其删除/进行清理?或者应该触发基于 I/2 的任何其他操作,但基于 I/3,不再适用,无论如何都要执行它们然后恢复它们?这听起来不对

4

2 回答 2

0

订单始终是指定客户订购的产品和数量的文档。由于这个原因,它是不可变的,所以我们不能谈论太多丰富的行为。但重要的是 Order 是一个业务概念,它不需要有 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 没有改变。

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

于 2013-05-17T05:41:52.393 回答
0

案例一/二

要解决为响应失败的域事件而过早发送电子邮件的问题,请查看此处

案例 1/3

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

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

于 2013-05-16T19:05:45.357 回答