在网上(和书本)上花费了无数小时试图得出关于这个主题的结论,查看了许多人的观点,以及试图权衡利弊的不同方面之后,我决定发布一些我认为的关键问题希望比我聪明的人回答:)
我阅读了 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,不再适用,无论如何都要执行它们然后恢复它们?这听起来不对