DDD Aggregate Component pattern in action
In my last post, I went on a long-winded rant on how the composition and the Aggregate Component pattern can ease the burden of the interaction between Entities and Services. The question comes up often on DDD or IoC forums:
How do I inject/use a Service/Repository inside an Entity?
You can get really fancy with your ORM or really fancy with your IoC Container to try and make your Entities join in an unholy matrimony. But there are other, better ways of skinning this OO cat.
###
The hard way
We may try to get these services injected through our ORM or IoC container, or we could try and do a static, opaque or service located dependency directly inside our entity:
public class Order { public decimal CalculateTotal() { decimal itemTotal = GetSubTotal(); decimal taxTotal = TaxService.CalculateTax(this); decimal discount = DiscountCalculator.FindDiscount(this); return itemTotal + taxTotal - discount; }
In this snippet, we have an Order entity that is apparently responsible for calculating up-to-date tax and discount information. We’ll get back to that cluster in a second. First order of business, how do we deal with these two dependencies, the TaxService and DiscountCalculator? A few options include:
- Static, opaque dependencies (seen above)
- Use service location, through a container or other means (ServiceLocator.GetInstance
()) - Pass dependencies in through the method, still using double-dispatch (CalculateTotal(ITaxService, IDiscountCalculator)
Each of these has its merits, but still becomes quite annoying. In the first approach, we’re back to just plain bad code, opaque, static, hard dependencies with tight coupling and no visibility from the outside of side effects or potential gotchas.
In the second approach, we’ve removed the tight coupling, but not the opaqueness. If it’s not plainly obvious to users of this class what it’s responsible for, that’s just more boring debugging and manual analysis on the developer’s side when things go wrong or we need to make a change. In the final option, we’ve now made our dependencies transparent, but now the burden of locating the dependencies is on the caller.
If we’re using dependency injection, no problem, but in this case we’re not injecting dependencies, we’re just passing them along. Do we really want our callers to have to figure out how the heck to locate an ITaxService or IDiscountCalculator? Probably not. Now, callers of Order not only need to know about what Order needs to do its work, but have to have these dependencies available somehow. We have better options, such as dependency injection for callers, but it’s still rather annoying that dependencies of Order are now affecting callers.
Studying our original domain model, I see a much larger issue – something’s wonky with our domain.
Fixing the model
When I was in e-commerce, we had a lot of prior art in looking at ordering and processing systems. In the above example, we have an Entity that has a moving target on what you would think would be one of its core attributes – its Total. An order with a changing total is bad news in the e-commerce world, as an Order is usually something that is placed and locked down. So what is this Order that we have here? Is it an OrderPlaced, or does it simply represent a request to place an order?
If we have a moving Total target, it’s far more likely that this is simply a request to place an order, not a placed order. Once an order is saved or processed, it usually enters into a larger saga/workflow/process, where the order moves from system to system (processing, payment, fulfillment, etc.). Maybe our company isn’t that complex, but we’d still not want customer complaining that the discount they saw when they placed the order yesterday is for some reason gone when the order is fulfilled today.
For these reasons, and others, we’re often not working with an Order on the customer side, but a Cart, an OrderRequest, or even a PotentialOrder. In those cases, it’s far more likely that these Entities, or something very close to them, will have the responsibility for calculating and displaying discounts and tax information.
But what exactly is an PotentialOrder? Does it have an identity? Is it ever persisted? Oftentimes, no. An PotentialOrder may be persisted, but more likely in a separate, locked-down model where totals and discounts can’t change the same way.
In comes the Aggregate Component
A PotentialOrder is a one-time only view of a potential order, built from a Cart (in our case, just a container of Products with quantities and a Customer), along with a snapshot of the tax and discount calculation at the time of request. The customer can view a PotentialOrder, then decide if they would like to continue the order and create an actual Order, where other tax and discount rules may come into play. Other names for PotentialOrder might be Quote, but the idea is still the same. An Order implies a request on the Customer side to make a transaction.
With this in mind, our PotentialOrder still has to do tax and discount calculations. In simple cases, we can just give the PotentialOrder the results of calculations, but in our case, we needed additional information about the tax and discounts, notably where they came from. Our PotentialOrder became an immutable Aggregate Component (looking like a Value Object, but bigger):
public class PotentialOrder { public PotentialOrder(Cart cart, ITaxProvider taxProvider, IDiscountFinder discountFinder) {
Our PotentialOrder takes exactly what it needs to run – the Cart, a tax provider and a discount finder. All transparent dependencies, each used for the PotentialOrder to do its job. I don’t have to look very far to find the real total calculations:
public decimal CalculateTotal() { decimal itemTotal = _cart.GetTotal(); decimal taxTotal = _taxProvider.Calculate(this); decimal discount = _discountFinder.SumDiscounts(this); return itemTotal + taxTotal - discount; }
We’re still using the same double-dispatch system from our opaque dependencies, but now we have a lot more leniency in how we can represent our calculations. Typically, we’ll combine some sort of Composite and Strategy pattern, such that we can combine many complex calculation algorithms into one interface, to be consumed by our PotentialOrder.
So how does our PotentialOrder get created? Not from a container, but through a specialized Factory instead:
public class PotentialOrderFactory { public PotentialOrder Create(Cart cart) { ITaxProvider taxman = _taxLocator.Find(cart); IDiscountFinder discounter = _discountBuilder.Build(cart); return new PotentialOrder(cart, taxman, discounter); }
The TaxLocator and DiscountBuilder encapsulate the potential complexities of resolving the correct tax and discount algorithms. Once built, the TaxProvider and DiscountFinder are immutable Value Objects, returning the same result every single time. One TaxLocator might be Texas Sales Tax, which is a percentage of the subtotal of a Cart. I do know that once my PotentialOrder is built, all of its algorithms are locked in and won’t change, no matter how complex they might get.
So why didn’t we just give the PotentialOrder its total? Personally, I like keeping behavior associated with an object, defined on that object. Putting the total calculations, as well as what’s needed to do so, on the PotentialOrder gets away from an anemic domain model. Before the Aggregate Component concept, behavior about a PotentialOrder was scattershot around the system, making it harder to grasp and talk about what a PotentialOrder was with the domain experts.
###
Keeping our model smart
I absolutely detest dumb domain models. The behavior is out there, I promise, but it’s probably in the wrong place. When we start seeing scenarios where we want our persistent entities calling out to services and repositories to do additional work, I see an opportunity to explore a larger, aggregate component model that pulls all these concepts into one cohesive place. DDD can lead to service-itis, with anemic domain models and a barrel full of services who seem awfully concerned about one or more entities.
Object-oriented design and modeling was refined and clarified with Evans’ book. But that doesn’t mean we’re supposed to be constrained to solely the patterns and names we found in the book.