问题描述
编辑:这不是理论上的冲突,而是实现级别的冲突.
另一个编辑: 问题不是将域模型作为纯数据/DTO与更富裕,更复杂的对象映射,其中Order order orderItems和一些计算逻辑.具体的问题是,例如,该订单需要从中国某些Web服务(例如)中获取订单信息的最新批发价格.因此,您可以进行一些春季服务,可以允许在中国进行此价格问题服务.订单具有计算方法,可以在每个OrderItem上迭代,获取最新价格,并将其添加到总数中.
那么,您如何确保每个订单都有对此价格问题服务的参考?您将如何通过去序列化,从DBS加载和新鲜实例来恢复它?这是我的确切问题.
简单的方法是传递对计算方法的引用,但是如果您的对象在其一生内部使用此服务,该怎么办?如果在10种方法中使用该怎么办?每次通过参考文献都会变得凌乱.
另一种方法是将计算方法从订单中移出并进入订单服务,但这打破了OO设计,我们朝着旧的"交易脚本"方式移动.
原始文章:
简短版本: 丰富的域对象需要引用许多组件,但是这些对象会持续或序列化,因此它们持有的任何引用到外部组件(在这种情况下为春季bean:服务,存储库,任何东西)都是瞬态的,并且被擦掉了.当对象被去序列化或从DB中加载时,需要重新注射它们,但这非常丑陋,我看不到一种优雅的方法.
更长的版本: 一段时间以来,我在春天的帮助下练习了松散的耦合和DI.它帮助我保持了可管理和可测试的事情.但是,不久前,我阅读了以域名驱动的设计和一些马丁·福勒(Martin Fowler).结果,我一直在尝试将我的域模型从简单的DTO(通常是表行的简单表示,只有数据逻辑)转换为更丰富的域模型.
随着我的域增长并承担新的职责,我的域对象开始需要我在春季上下文中我拥有的一些bean(服务,存储库,组件).这很快成为一场噩梦,也是转换为丰富域设计的最困难的部分之一.
基本上,我在某些地方手动将对应用程序上下文的引用注入我的域:
- 当对象从存储库或其他负责任实体加载时,由于组件引用是瞬态的,并且显然不会持久
- 从工厂创建对象时,由于新创建的对象缺少组件参考
- 当对象在石英作业或其他地方进行除外序列化时
首先,这很丑陋,因为我将对象传递给应用程序上下文参考,并期望它通过名称引用到所需的组件.这不是注射,而是直接拉.
第二,这是丑陋的代码
第三,这是错误的,因为我必须记住在所有这些对象中注入所有这些位置,这比听起来要难.
必须有更好的方法,我希望您能为此提供一些启示.
推荐答案
我找到了答案,至少对于使用Spring的人:
来依赖性注入域对象其他推荐答案
我敢说,在拥有"贫血域模型"与将所有服务塞入您的域对象之间存在许多灰色阴影.而且,至少在我的经验中,通常,一个对象实际上只不过是数据而已.例如,每当可以在该特定对象上执行的操作取决于其他对象和某些本地化上下文时,例如地址.
在我对网络上以域驱动的文献的评论中,我发现了很多模糊的想法和著作,但是我找不到方法和操作之间边界的适当的,不平淡无奇的例子撒谎,而且,如何通过当前的技术堆栈实施它.因此,出于这个答案的目的,我将撰写一个小例子来说明我的观点:
考虑订单和订购的古老例子. "贫血"域模型看起来像:
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.理论上的声音模型是什么好处,如果报告生成需要年龄?
当然,整个问题非常复杂,我可以一次坐着写或考虑的更多.而且,我不是从权威的立场上讲话,而是在部署业务应用程序时简单的日常练习.希望您会从这个答案中得到一些东西.随时提供一些您要处理的内容的其他细节和示例...
编辑:关于PriceQuery服务,以及在计算总数后发送电子邮件的示例,我将在以下区别:
- 在价格计算后应发送电子邮件 的事实
- 应发送订单的哪一部分? (这也可能包括电子邮件模板)
- 发送电子邮件的实际方法
此外,人们不得不怀疑,正在向电子邮件发送Order的固有能力,或者是可以使用它的另一件事,例如坚持下去,序列化,序列化为不同的格式(XML,CSV,Excel)等
我将要做的事情,以及我认为的好方法是以下内容.定义一个接口封装准备和发送电子邮件的操作:
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(); }
最后,您应该对应用程序操作有一个立面,这是对用户操作的实际响应.我认为,这是您应该(通过春季DI)获得服务的实际实施的地方.例如,这可以是Spring MVC Controller类:
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(...); }
这是您通过这种方法得到的:
- 域对象不包含服务,它们使用它们
- 域对象与实际服务实现(例如SMTP,在单独的线程等中发送等)分离,界面机制的性质
- 服务接口是通用的,可重复使用的,但不知道任何实际域对象.例如,如果订单获得额外的字段,则只需要更改Order类.
- 您可以轻松模拟服务,并轻松测试域对象
- 您可以轻松测试实际服务实施
我不知道这是否是按照某些大师的标准,但这是一种脚踏实地的方法,在实践中运作良好.
其他推荐答案
chatchinig
如果您的订单需要发送怎么办 每次总计是 计算?
我会采用活动.
如果当订单计算总数时对您有一定的含义,请让它作为eventdispatcher.raiseevent(new ComputeDtotaleVent(this))提出事件.
然后,您会收听此类事件,并按照以前所说的订单进行回调,以使其格式化电子邮件模板,然后发送.
您的域对象仍然很苗条,不了解您的要求.
简而言之,将您的问题分为2个要求:
- 我想知道订单何时计算总数;
- 我想在订单有(新的)总计(
问题描述
Edit: This is not a conflict on the theoretical level but a conflict on an implementation level.
Another Edit: The problem is not having domain models as data-only/DTOs versus richer, more complex object map where Order has OrderItems and some calculateTotal logic. The specific problem is when, for example, that Order needs to grab the latest wholesale prices of the OrderItem from some web service in China (for example). So you have some Spring Service running that allows calls to this PriceQuery service in China. Order has calculateTotal which iterates over every OrderItem, gets the latest price, and adds it to the total.
So how would you ensure that every Order has a reference to this PriceQuery service? How would you restore it upon de-serializations, loading from DBs, and fresh instantiations? This is my exact question.
The easy way would be to pass a reference to the calculateTotal method, but what if your Object uses this service internally throughout its lifetime? What if it's used in 10 methods? It gets messy to pass references around every time.
Another way would be to move calculateTotal out of the Order and into the OrderService, but that breaks OO design and we move towards the old "Transaction Script" way of things.
Original post:
Short version: Rich domain objects require references to many components, but these objects get persisted or serialized, so any references they hold to outside components (Spring beans in this case: services, repositories, anything) are transient and get wiped out. They need to be re-injected when the object is de-serialized or loaded from the DB, but this is extremely ugly and I can't see an elegant way to do it.
Longer version: For a while now I've practiced loose coupling and DI with the help of Spring. It's helped me a lot in keeping things manageable and testable. A while ago, however, I read Domain-Driven Design and some Martin Fowler. As a result, I've been trying to convert my domain models from simple DTOs (usually simple representations of a table row, just data no logic) into a more rich domain model.
As my domain grows and takes on new responsibilities, my domain objects are starting to require some of the beans (services, repositories, components) that I have in my Spring context. This has quickly become a nightmare and one of the most difficult parts of converting to a rich domain design.
Basically there are points where I am manually injecting a reference to the application context into my domain:
- when object is loaded from Repository or other responsible Entity since the component references are transient and obviously don't get persisted
- when object is created from Factory since a newly created object lacks the component references
- when object is de-serialized in a Quartz job or some other place since the transient component references get wiped
First, it's ugly because I'm passing the object an application context reference and expecting it to pull out by name references to the components it needs. This isn't injection, it's direct pulling.
Second, it's ugly code because in all of those mentioned places I need logic for injecting an appContext
Third, it's error prone because I have to remember to inject in all those places for all those objects, which is harder than it sounds.
There has got to be a better way and I'm hoping you can shed some light on it.
推荐答案
I've found the answer, at least for those using Spring:
6.8.1. Using AspectJ to dependency inject domain objects with Spring
其他推荐答案
I would venture to say that there are many shades of gray between having an "anemic domain model" and cramming all of your services into your domain objects. And quite often, at least in business domains and in my experience, an object might actually be nothing more than just the data; for example, whenever the operations that can be performed on that particular object depend on multitude of other objects and some localized context, say an address for example.
In my review of the domain-driven literature on the net, I have found a lot of vague ideas and writings, but I was not unable to find a proper, non-trivial example of where the boundaries between methods and operations should lie, and, what's more, how to implement that with current technology stack. So for the purpose of this answer, I will make up a small example to illustrate my points:
Consider the age-old example of Orders and OrderItems. An "anemic" domain model would look something like:
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; }
In my opinion, the point of the domain-driven design is to use classes to better model the relationships between entities. So, an non-anemic model would look something like:
class Order { Long orderId; Date orderDate; User receivedBy; Set<OrderItem> items; } class OrderItem { Order order; Product product; BigDecimal amount; BigDecimal price; }
Supposedly, you would be using an ORM solution to do the mapping here. In this model, you would be able to write a method such as Order.calculateTotal(), that would sum up all the amount*price for each order item.
So, the model would be rich, in a sense that operations that make sense from a business perspective, like calculateTotal, would be placed in an Order domain object. But, at least in my view, domain-driven design does not mean that the Order should know about your persistence services. That should be done in a separate and independent layer. Persistence operations are not part of the business domain, they are the part of the implementation.
And even in this simple example, there are many pitfalls to consider. Should the entire Product be loaded with each OrderItem? If there is a huge number of order items, and you need a summary report for a huge number of orders, would you be using Java, loading objects in memory and invoking calculateTotal() on each order? Or is an SQL query a much better solution, from every aspect. That is why a decent ORM solution like Hibernate, offers mechanisms for solving precisely these kind of practical problems: lazy-loading with proxies for the former and HQL for the latter. What good would be a theoretically sound model be, if report generation takes ages?
Of course, the entire issue is quite complex, much more that I'm able to write or consider in one sitting. And I'm not speaking from a position of authority, but simple, everyday practice in deploying business apps. Hopefully, you'll get something out of this answer. Feel free to provide some additional details and examples of what you're dealing with...
Edit: Regarding the PriceQuery service, and the example of sending an email after the total has been calculated, I would make a distinction between:
- the fact that an email should be sent after price calculation
- what part of an order should be sent? (this could also include, say, email templates)
- the actual method of sending an email
Furthermore, one has to wonder, is sending of an email an inherent ability of an Order, or yet another thing that can be done with it, like persisting it, serialization to different formats (XML, CSV, Excel) etc.
What I would do, and what I consider a good OOP approach is the following. Define an interface encapsulating operations of preparing and sending an email:
interface EmailSender { public void setSubject(String subject); public void addRecipient(String address, RecipientType type); public void setMessageBody(String body); public void send(); }
Now, inside Order class, define an operation by which an order "knows" how to send itself as an email, using an email sender:
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(); }
Finally, you should have a facade towards your application operations, a point where the actual response to user action happens. In my opinion, this is where you should obtain (by Spring DI) the actual implementations of services. This can, for example, be the Spring MVC Controller 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(...); }
Here's what you get with this approach:
- domain objects don't contain services, they use them
- domain objects are decoupled from actual service implementation (e.g. SMTP, sending in separate thread etc.), by the nature of the interface mechanism
- services interfaces are generic, reusable, but don't know about any actual domain objects. For example, if order gets an extra field, you need change only the Order class.
- you can mock services easily, and test domain objects easily
- you can test actual services implementations easily
I don't know if this is by standards of certain gurus, but it a down-to-earth approach that works reasonably well in practice.
其他推荐答案
Regardinig
What if your Order needs to send out an e-mail every time the total is calculated?
I would employ events.
If it has some meaning for you when an order computes its total, let it raise an event as eventDispatcher.raiseEvent(new ComputedTotalEvent(this)).
Then you listen for this type of events, and callback your order as said before to let it format an email template, and you send it.
Your domain objects remains lean, with no knowledge about this your requirement.
In short, split your problem into 2 requirements:
- I want to know when an order computes its total;
- I want to send an email when an order has a (new and different) total;