Java 如何解决松耦合/依赖项注入与富域模型之间的冲突?
编辑:这不是理论层面的冲突,而是实施层面的冲突 另一编辑: 问题在于没有将域模型作为仅数据/DTO,而不是更丰富、更复杂的对象映射,其中Order具有OrderItems和一些calculateTotal逻辑。具体的问题是,例如,该订单需要从中国的某个web服务获取OrderItem的最新批发价格(例如)。因此,您运行了一些Spring服务,允许在中国调用此PriceQuery服务。Order具有calculateTotal,它迭代每个OrderItem,获取最新价格,并将其添加到总价中 那么,您如何确保每个订单都有对该PriceQuery服务的引用呢?在反序列化、从DBs加载和新实例化后,您将如何恢复它?这正是我的问题 简单的方法是传递对calculateTotal方法的引用,但是如果您的对象在其整个生命周期中在内部使用此服务,该怎么办?如果它被用在10种方法中呢?每次传递参考资料都很麻烦 另一种方法是将calculateTotal从订单中移入OrderService,但这打破了OO设计,我们转向了旧的“事务脚本”方式 原职: 短版: 富域对象需要对许多组件的引用,但这些对象会被持久化或序列化,因此它们对外部组件(本例中为SpringBean:服务、存储库等)的任何引用都是暂时的,并且会被删除。当对象被反序列化或从DB加载时,它们需要被重新注入,但这非常难看,我看不到一种优雅的方法来做到这一点 较长版本: 有一段时间,我在Spring的帮助下练习松耦合和DI。这对我保持事情的可管理性和可测试性有很大帮助。然而,不久前,我阅读了领域驱动设计和一些Martin Fowler的文章。因此,我一直在尝试将我的域模型从简单的DTO(通常是表行的简单表示,只是数据而不是逻辑)转换为更丰富的域模型 随着我的域的增长和承担新的责任,我的域对象开始需要我在Spring上下文中拥有的一些bean(服务、存储库、组件)。这很快就成为了一场噩梦,也是转换为富领域设计最困难的部分之一 基本上,我会手动将对应用程序上下文的引用注入到我的域中:Java 如何解决松耦合/依赖项注入与富域模型之间的冲突?,java,spring,dependency-injection,domain-driven-design,Java,Spring,Dependency Injection,Domain Driven Design,编辑:这不是理论层面的冲突,而是实施层面的冲突 另一编辑: 问题在于没有将域模型作为仅数据/DTO,而不是更丰富、更复杂的对象映射,其中Order具有OrderItems和一些calculateTotal逻辑。具体的问题是,例如,该订单需要从中国的某个web服务获取OrderItem的最新批发价格(例如)。因此,您运行了一些Spring服务,允许在中国调用此PriceQuery服务。Order具有calculateTotal,它迭代每个OrderItem,获取最新价格,并将其添加到总价中 那么,
- 当从存储库或其他负责实体加载对象时,因为组件引用是暂时的,显然不会持久化
- 从工厂创建对象时,因为新创建的对象缺少组件引用
- 当对象在Quartz作业或其他位置反序列化时,因为瞬态组件引用被擦除
必须有更好的方法,我希望您能对此有所了解。也许您想要的是一种on引用对象,它可以序列化为全局引用(例如URI),并且在其他地方反序列化时能够恢复为代理。该模式可能有助于您的场景。查看Jeremy Miller撰写的文章,他在文章中讨论了这种模式。我想冒昧地说,在拥有“贫血的域模型”和将所有服务塞进域对象之间存在着许多灰色地带。而且通常,至少在业务领域和我的经验中,对象实际上可能只是数据;例如,只要可以在特定对象上执行的操作依赖于大量其他对象和某些本地化上下文,例如地址 在我对网络上领域驱动的文献的回顾中,我发现了许多模糊的想法和著作,但我并没有找不到一个恰当的、非平凡的例子,说明方法和操作之间的界限应该在哪里,更重要的是,如何用当前的技术堆栈实现这一点。因此,为了回答这个问题,我将举一个小例子来说明我的观点: 考虑古老的订单和订单项示例。“贫血”域模型看起来像:
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;
}
在我看来,领域驱动设计的要点是使用类来更好地建模实体之间的关系。因此,一个非贫血模型看起来像:
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;
}
现在,在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();
}
最后,您应该有一个面向您的应用程序操作的外观,这是对用户操作的实际响应发生的地方。在我看来,这是您应该(通过SpringDI)获得服务的实际实现的地方。例如,它可以是Spring MVC控制器
类:
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(...);
}
以下是您通过这种方法得到的结果: