如何解决松散耦合/依赖注入和富域模型之间的冲突?

2024-01-17

Edit:这不是理论层面的冲突,而是实施层面的冲突。

另一个编辑:问题在于域模型不作为纯数据/DTO,而不是更丰富、更复杂的对象映射(其中 Order 具有 OrderItems 和一些calculateTotal 逻辑)。具体问题是,例如,该 Order 需要从中国的某些 Web 服务获取 OrderItem 的最新批发价格(例如)。因此,您运行了一些 Spring 服务,允许在中国调用此 PriceQuery 服务。 Order 具有calculateTotal,它会迭代每个OrderItem,获取最新价格,并将其添加到总计中。

那么,如何确保每个订单都引用此 PriceQuery 服务呢?在反序列化、从数据库加载和新实例化时如何恢复它?这就是我的确切问题。

最简单的方法是传递对calculateTotal方法的引用,但是如果您的对象在其整个生命周期内部使用此服务怎么办?如果用10种方法呢?每次传递引用都会变得很混乱。

另一种方法是将calculateTotal 从Order 移出并移入OrderService,但这破坏了OO 设计,我们转向旧的“事务脚本”方式。

原帖:

简洁版本:丰富的域对象需要对许多组件的引用,但这些对象会被持久化或序列化,因此它们对外部组件(在本例中为 Spring bean:服务、存储库等任何东西)所持有的任何引用都是暂时的并会被清除。当对象被反序列化或从数据库加载时,它们需要重新注入,但这非常丑陋,我看不到一种优雅的方法来做到这一点。

更长的版本:一段时间以来,我在 Spring 的帮助下练习了松耦合和 DI。它对我保持事情的可管理性和可测试性有很大帮助。然而不久前,我读了《领域驱动设计》和一些 Martin Fowler 的文章。因此,我一直在尝试将域模型从简单的 DTO(通常是表行的简单表示,只有数据,没有逻辑)转换为更丰富的域模型。

随着我的域不断发展并承担新的职责,我的域对象开始需要我在 Spring 上下文中拥有的一些 bean(服务、存储库、组件)。这很快就成为一场噩梦,也是转换为富域设计最困难的部分之一。

基本上,我在某些地方手动将对应用程序上下文的引用注入到我的域中:

  • 当从存储库或其他负责实体加载对象时,因为组件引用是暂时的并且显然不会持久化
  • 当从 Factory 创建对象时,因为新创建的对象缺少组件引用
  • 当对象在 Quartz 作业或其他地方反序列化时,因为瞬态组件引用被擦除

首先,它很丑陋,因为我向对象传递了一个应用程序上下文引用,并期望它通过名称引用拉出它所需的组件。这不是注射,而是直接拉动。

其次,它是丑陋的代码,因为在所有提到的地方我需要注入 appContext 的逻辑

第三,它很容易出错,因为我必须记住为所有这些对象注入所有这些位置,这比听起来更难。

一定有更好的方法,我希望你能对此有所启发。


我敢说,拥有“贫乏的域模型”和将所有服务塞进域对象之间存在许多灰色地带。通常,至少在业务领域和根据我的经验,对象实际上可能只不过是数据;例如,每当可以对该特定对象执行的操作依赖于大量其他对象和一些本地化上下文(例如地址)时。

在我对网上领域驱动文献的回顾中,我发现了很多模糊的想法和著作,但我并非无法找到一个适当的、重要的例子来说明方法和操作之间的界限应该在哪里,并且,更重要的是,如何用当前的技术栈来实现它。因此,为了回答这个问题,我将举一个小例子来说明我的观点:

考虑一下古老的 Orders 和 OrderItems 示例。 “贫乏”的领域模型看起来像这样:

class Order {
    Long orderId;
    Date orderDate;
    Long receivedById;  // user which received the order
 }

 class OrderItem {
     Long orderId;      // order to which this item belongs
     Long productId;    // product id
     BigDecimal amount;
     BigDecimal price;
 }

在我看来,领域驱动设计的要点是使用类来更好地建模实体之间的关系。因此,非贫血模型看起来像:

class Order {
   Long orderId;
   Date orderDate;
   User receivedBy;
   Set<OrderItem> items;
}

class OrderItem {
   Order order;
   Product product;
   BigDecimal amount;
   BigDecimal price;
}

假设您将使用 ORM 解决方案来进行此处的映射。在这个模型中,您将能够编写一个方法,例如Order.calculateTotal(),这将总结所有amount*price对于每个订单项目。

因此,该模型将会很丰富,从某种意义上说,从业务角度来看有意义的操作,例如calculateTotal,将被放置在Order域对象。但是,至少在我看来,领域驱动设计并不意味着Order应该了解您的持久性服务。这应该在单独且独立的层中完成。持久化操作不是业务领域的一部分,而是实现的一部分。

即使在这个简单的示例中,也有许多陷阱需要考虑。如果整个Product装载着每一个OrderItem?如果有大量订单项,并且您需要大量订单的汇总报告,您是否会使用 Java,将对象加载到内存中并调用calculateTotal()每个订单?或者从各个方面来看,SQL 查询都是更好的解决方案。这就是为什么像 Hibernate 这样的像样的 ORM 解决方案提供了精确解决此类实际问题的机制:前者使用代理延迟加载,后者使用 HQL。如果报告生成需要很长时间,那么理论上合理的模型有什么用呢?

当然,整个问题相当复杂,远非我能够一次性写出或考虑的。我并不是站在权威的立场上发言,而是部署业务应用程序的简单日常实践。希望您能从这个答案中得到一些东西。请随意提供一些额外的细节和您正在处理的示例......

Edit: 关于PriceQuery服务,以及计算总数后发送电子邮件的示例,我会区分:

  1. 价格计算后应发送电子邮件的事实
  2. 应发送订单的哪一部分? (这还可能包括电子邮件模板)
  3. 发送电子邮件的实际方法

此外,人们不得不想知道,发送电子邮件是否是一个人固有的能力?Order,或者可以用它完成的另一件事,例如持久化、序列化为不同格式(XML、CSV、Excel)等。

我会做什么,以及我认为好的 OOP 方法如下。定义一个接口,封装准备和发送电子邮件的操作:

 interface EmailSender {
     public void setSubject(String subject);
     public void addRecipient(String address, RecipientType type);
     public void setMessageBody(String body);
     public void send();
 }

现在,里面Order类,定义一个操作,通过该操作订单“知道”如何使用电子邮件发送器将自身作为电子邮件发送:

class Order {
...
    public void sendTotalEmail(EmailSender sender) {
        sender.setSubject("Order " + this.orderId);
        sender.addRecipient(receivedBy.getEmailAddress(), RecipientType.TO);
        sender.addRecipient(receivedBy.getSupervisor().getEmailAddress(), RecipientType.BCC);
        sender.setMessageBody("Order total is: " + calculateTotal());
        sender.send();
    }

最后,您应该有一个应用程序操作的外观,这是对用户操作进行实际响应的发生点。在我看来,这是您应该(通过 Spring DI)获取服务的实际实现的地方。例如,这可以是 Spring MVCController class:

public class OrderEmailController extends BaseFormController {
   // injected by Spring
   private OrderManager orderManager;  // persistence
   private EmailSender emailSender;    // actual sending of email

public ModelAndView processFormSubmission(HttpServletRequest request,
                                          HttpServletResponse response, ...) {
    String id = request.getParameter("id");
    Order order = orderManager.getOrder(id);
    order.sendTotalEmail(emailSender);

    return new ModelAndView(...);
}

通过这种方法您可以获得以下结果:

  1. 域对象不包含服务,它们use them
  2. 根据接口机制的性质,域对象与实际服务实现(例如 SMTP、在单独线程中发送等)分离
  3. 服务接口是通用的、可重用的,但不知道任何实际的域对象。例如,如果订单有一个额外的字段,您只需更改Order class.
  4. 您可以轻松模拟服务,并轻松测试域对象
  5. 您可以轻松测试实际的服务实现

我不知道这是否符合某些大师的标准,但这是一种在实践中相当有效的脚踏实地的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何解决松散耦合/依赖注入和富域模型之间的冲突? 的相关文章

  • 如何在 Play java 中创建数据库线程池并使用该池进行数据库查询

    我目前正在使用 play java 并使用默认线程池进行数据库查询 但了解使用数据库线程池进行数据库查询可以使我的系统更加高效 目前我的代码是 import play libs Akka import scala concurrent Ex
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • 反射找不到对象子类型

    我试图通过使用反射来获取包中的所有类 当我使用具体类的代码 本例中为 A 时 它可以工作并打印子类信息 B 扩展 A 因此它打印 B 信息 但是当我将它与对象类一起使用时 它不起作用 我该如何修复它 这段代码的工作原理 Reflection
  • 路径中 File.separator 和斜杠之间的区别

    使用有什么区别File separator和一个正常的 在 Java 路径字符串中 与双反斜杠相反 平台独立性似乎不是原因 因为两个版本都可以在 Windows 和 Unix 下运行 public class SlashTest Test
  • 斯坦福 NLP - 处理文件列表时 OpenIE 内存不足

    我正在尝试使用斯坦福 CoreNLP 中的 OpenIE 工具从多个文件中提取信息 当多个文件 而不是一个 传递到输入时 它会给出内存不足错误 All files have been queued awaiting termination
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • Java按日期升序对列表对象进行排序[重复]

    这个问题在这里已经有答案了 我想按一个参数对对象列表进行排序 其日期格式为 YYYY MM DD HH mm 按升序排列 我找不到正确的解决方案 在 python 中使用 lambda 很容易对其进行排序 但在 Java 中我遇到了问题 f
  • 如何将 pfx 文件转换为 jks,然后通过使用 wsdl 生成的类来使用它来签署传出的肥皂请求

    我正在寻找一个代码示例 该示例演示如何使用 PFX 证书通过 SSL 访问安全 Web 服务 我有证书及其密码 我首先使用下面提到的命令创建一个 KeyStore 实例 keytool importkeystore destkeystore
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • 如何在控制器、服务和存储库模式中使用 DTO

    我正在遵循控制器 服务和存储库模式 我只是想知道 DTO 在哪里出现 控制器应该只接收 DTO 吗 我的理解是您不希望外界了解底层域模型 从领域模型到 DTO 的转换应该发生在控制器层还是服务层 在今天使用 Spring MVC 和交互式
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • Java执行器服务线程池[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果我使用 Executor 框架在
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • java.lang.IllegalStateException:驱动程序可执行文件的路径必须由 webdriver.chrome.driver 系统属性设置 - Similiar 不回答

    尝试学习 Selenium 我打开了类似的问题 但似乎没有任何帮助 我的代码 package seleniumPractice import org openqa selenium WebDriver import org openqa s

随机推荐

  • 在纯 JavaScript 中加载多个 JSON 文件

    我是 JavaScript 新手 我已经了解如何使用 JSON Parse 从 JSON 文件创建对象 现在我需要将多个本地 JSON 加载到数组中 我已经在谷歌上搜索我的问题一段时间了 但我发现的所有内容都与单个 JSON 文件相关 有没
  • 用于 CSV 的基于 Spring Batch Java 的 FileItemWriter

    我有一个包含 ItemWriter 的 Spring Batch 服务 用于将数据写入 CSV 我使用了 Spring Batch 指南给出的示例 https spring io guides gs batch processing htt
  • 在 JavaScript 中将项目推入多维树状结构

    我有一系列对象 它们的情况如下 var obj id 23 name Test1 children id 24 name Test2 children id 25 name Test2 children 每个孩子可以有多个子孩子 所以基本上
  • 如何使用 HEAD 和任何合并工具解决所有冲突

    因此 由于某种原因 我与新的合并修补程序发生了很多冲突 实际 手动 更改的文件没有冲突 所有冲突都在修复过程中未触及的文件中 显然是空格问题 稍后我会尝试解决该问题 但现在我需要合并修补程序并进行部署 如何解决所有冲突以使用 HEAD 版本
  • 在 64 位操作系统中使用 glMultiDrawElements

    我最近从32位环境迁移到64位环境 除了一个问题之外 一切都很顺利 glMultiDrawElements使用一些在 64 位操作系统下不进行一些调整就无法工作的数组 glMultiDrawElements GL LINE LOOP fCo
  • Vavr 对象的序列化器/反序列化器

    您好 我正在尝试将 vavr 添加到我的项目中 现在我正在努力解决 Vavr List 对象的正确序列化问题 下面是我的控制器 import io vavr collection List GetMapping value xxx publ
  • 具有 varchar id 的 Doctrine2 实体不会将 id 插入数据库

    我正在尝试在 ZF2 应用程序中为 Doctrine2 创建实体 我的实体应该有 id varchar 15 但是当我尝试创建新行时 doctrine2 不会将此 ID 推送到数据库中 在实体生成的类中我有这个 Checkpoints OR
  • React - 模块解析失败

    从命令行运行 webpack 时 我一整天都遇到此错误 ERROR in index js Module parse failed home kuro Workspace ExpressJS corate src index js Line
  • Angular.JS:为什么无法编辑输入?

    这是一个奇怪的问题 代码很简单 HTML 代码 ul ul
  • 如果内容超过一定限制,PHP 电子邮件会中断

    我正在使用 PHPmail 发送每日通知电子邮件 但我注意到 如果存储邮件内容的字符串太长 则电子邮件会被中断 即收到的电子邮件中只会显示部分内容 当内容字符串很短时 电子邮件将是完整的 看来 PHP 对字符串的长度有一些限制 我尝试使用e
  • 如何在单击时将按钮图像从打开切换到关闭,反之亦然?

    我一直在尝试通过添加打开状态的图像和添加关闭状态的图像来将按钮的状态从打开切换到关闭 反之亦然 我尝试通过 xml 但是我只能在单击时临时切换它 通过使用按下 焦点等 这是相同的代码 片段 贾斯汀
  • Emacs:仅在迷你缓冲区中禁用行截断

    我在用IDO模式 http www emacswiki org emacs InteractivelyDoThings用于 Emacs 23 中的文件和缓冲区切换 如果目录中有超过一行的文件 以下选项允许调整迷你缓冲区的大小 setq re
  • Windows 中的自签名证书无需 makecert?

    我们有一个收缩包装类型的 Windows 服务器应用程序 我们需要在服务器上创建一个自签名证书以供某些 WCF Web 服务使用 从我们在网络上的搜索来看 Microsoft PlatformSDK 中的 makecert 实用程序似乎无法
  • Python 类中的公共变量?

    我现在正在自学 Python 课程 并发现了这个页面 http www tutorialspoint com python python classes objects htm http www tutorialspoint com pyt
  • Flutter:如何获取 Firestore 中集合的所有文档名称

    我使用 Firestore 在 Flutter 中制作了一个应用程序 现在我将浏览集合中的所有文档 我想获取文档名称 id 和文档字段 并对其执行某些操作 我已经制作了一个显示数据的列表视图 但我无法用它做一些事情 例如 将其添加到列表或其
  • 多线程应用程序执行 onclickBtn 后挂起

    我正在 javaFx 中编写一个天气应用程序 从 openweather org 获取数据 从 openweather 获取 JSON 的整个代码工作正常 也将 JSON 数据转换为对象 我使用lambda表达式来实现Runnable in
  • Python 习语与多次调用 os.path.dirname 获得相同的结果?

    我发现自己需要在源树中获取 python 文件的父目录 该源树是具有一定规律性的多个目录 必须多次调用 dirname 很笨拙 我环顾四周 很惊讶没有找到关于此的帖子 一般场景是 import os path as op third deg
  • 如何在 MSVC 下检测 C++11 的 noexcept 功能?

    我正在使用 C 库 该库的最低要求是 C 03 我在 Visual Studio 2015 下收到一些关于抛出析构函数的警告 algparam h 271 warning C4297 AlgorithmParametersBase Algo
  • 前端和后端可以共享同一个package.json吗?

    我有一个小型个人项目 正在一个存储库中开发 后端是 Node js 服务器 前端是 Vue js 应用程序 我希望它们共享相同的 package json 我想这样做的唯一原因是因为我想使用 scripts 一个通用的 package js
  • 如何解决松散耦合/依赖注入和富域模型之间的冲突?

    Edit 这不是理论层面的冲突 而是实施层面的冲突 另一个编辑 问题在于域模型不作为纯数据 DTO 而不是更丰富 更复杂的对象映射 其中 Order 具有 OrderItems 和一些calculateTotal 逻辑 具体问题是 例如 该