How can I resolve the conflict between loose coupling/dependency injection and a rich domain model? How can I resolve the conflict between loose coupling/dependency injection and a rich domain model? spring spring

How can I resolve the conflict between loose coupling/dependency injection and a rich domain model?


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:

  1. the fact that an email should be sent after price calculation
  2. what part of an order should be sent? (this could also include, say, email templates)
  3. 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 emailpublic 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:

  1. domain objects don't contain services, they use them
  2. domain objects are decoupled from actual service implementation (e.g. SMTP, sending in separate thread etc.), by the nature of the interface mechanism
  3. 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.
  4. you can mock services easily, and test domain objects easily
  5. 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;