Re: [DDD/CQRS] Injecting Domain Services into Entity Methods vs Constructors

2,014 views
Skip to first unread message

Kijana Woodard

unread,
Jan 4, 2016, 8:33:19 PM1/4/16
to ddd...@googlegroups.com
The case where method injection shows merit is when you find you have the ability to pass null into a constructor arg and some public method still works. It's just cruft in the case of that method.

But I'd rather calculate taxes before constructing the command that will result in an order.
As you say, complex logic and all, but to an order, there are just line items with tax codes and/or some kind of tax rate summary for the entire order. How that happens is not really a part of "the" order. 

Another example to illustrate: discounts. You'd have to inject a discounting service into order.

If you pull all that out of order, you can construct a command with line items and tax totals without "Order" having to understand every new concept the business introduces. 

Another reason for investigation: the order is passing itself to a function in order to update itself. Either that or the IOrderTaxCalculationService is performing "live queries" which means the tax situation of a six month old order could be changed by a method call. Seems strange.

On Mon, Jan 4, 2016 at 4:29 PM, Michael Clark <mclar...@gmail.com> wrote:
I'm in the beginning stages of trying to apply DDD to a project that I'm working on that has an extremely anemic domain model.  Right now I'm trying to currently figure out the best way to offload complex business logic to domain services, while keeping behavior explicitly modeled on my entity at the same time.

For example, let's say I'm working with the standard Order aggregate and I want to encapsulate the behavior of calculating taxes for the order.  My first pass at the interface might look like.


class Order {
   
Taxes CalculateTaxes() {}
}


As an aside, I'm requiring the client to call CalculateTaxes() explicitly whenever an updated tax amount is needed, rather than storing taxes directly in the order itself.  This is to avoid the overhead of having to maintain an invariant like "Tax Amount will be the sum of order item totals * effective tax rate".  I don't to have to calculate taxes every time I modify something on the order, and I definitely don't want to be in a situation where the tax amount is stale or incorrect.  I only point this out in case there's something fundamentally wrong with my approach, which could be causing my confusion in the first place.

Calculating taxes in my domain can get fairly complex and also requires remote service calls.  That's definitely not something I would like to put right in my entity, so I'll create a domain service to handle it called IOrderTaxCalculationService:


interface OrderTaxCalculationService {
   
Taxes CalculateTaxes(Order order);
}


So far that seems pretty straightforward to me, but now I get confused as to the best way to use this fancy new service and keep behavior on my entity at the same time.  From what I've read, general wisdom seems to discourage me from injecting this service into the constructor of my entity, and instead favor passing the service in as an argument to my entity method


class Order {
   
Taxes CalculateTaxes(IOrderTaxCalculationService service) {
       
return service.CalculateTaxes(this);
   
}
}


However, I don't really see the benefit to this.  Assuming I'm using an IoC container, injecting the dependency in the constructor rather than the method wouldn't cause anymore coupling right? It also just forces any consumer of the Order aggregate to inject this dependency themselves, which seems to be an extra maintenance burden.  For the sake of argument, assume I don't need to swap out different implementations of the tax calculator at runtime.

What don't I understand about the method injection approach that would make it better than constructor injection in the general case?

Thanks,

Mike










--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Michael Clark

unread,
Jan 5, 2016, 11:10:32 PM1/5/16
to DDD/CQRS
So I can see your point about ultimately building a SubmitOrder command with a completed order with all line items specified.  I also understand why you'd want to keep the Order from having to know about the IOrderTaxCalculationService or IOrderDiscountService.  I suppose I'm worried that making Order completely unaware that taxes or discounts need to be applied leads to anemia, and may be over thinking it.

I guess what I'm actually trying to model is the process of actually building the command, so maybe what I'm really talking about is the ShoppingCart. My users want to see a running total as they go, including taxes.  That means whenever someone calls AddItem(item) or RemoveItem(item) I would need to recalculate the taxes.  Same goes for discounts and special promotions.  Let's say you get a 10% discount when you add 10 or more items.  I'd have to add the discount line item when the 10th item is added, and remove it if the # of items dropped down to 9.  Or maybe when you add a certain product I add another promotional one.  Then I think it makes method injection way less desirable:

AddItem(item, taxService, discountService)    // Or any other concept the business will come up with
RemoveItem(item, taxService, discountService)

But keeping the ShoppingCart from knowing about these services altogether requires that every call to Add/Remove be followed up with these separate calls, which is also cumbersome:

cart.AddItem(item);
discountService
.ApplyDiscounts(cart);
taxService
.CalculateTaxes(cart);

It gets even worse if there are other operations that might affect taxes, like changing the shipping address to a different state.  

Is there possibly a different way I should be thinking of applying business logic to this command building process?

Ben Kloosterman

unread,
Jan 5, 2016, 11:42:28 PM1/5/16
to ddd...@googlegroups.com
"cart.AddItem(item);
discountService
.ApplyDiscounts(cart);
taxService
.CalculateTaxes(cart);"

This is a good thing  the domain(s)  /Aggregates  are consistent and organized , multiple steps but clean ones is what i would like to see .... if its too much trouble on the client , then group then in the facade  / helper but not the domain ..

Danil Suits

unread,
Jan 6, 2016, 12:33:28 AM1/6/16
to DDD/CQRS
"My users want to see a running total as they go, including taxes."

That's a query, isn't it?

(Warning - novice languaging ahead)

Just reviewing your fragments, here are some bits that strike me as odd.

From your original email

class Order {
   
Taxes CalculateTaxes(IOrderTaxCalculationService service) {
       
return service.CalculateTaxes(this);
   
}
}


That code fragment seems to imply that the service can query the Order.  I don't expect aggregates to support queries, I expect them to pass their current state (the relevant parts of it) to the domain services.  So, guessing that tax calculation is based on the aggregate collection of items, I'd expect a signature like

class Order {
   
Taxes CalculateTaxes(IOrderTaxCalculationService service) {

       
return service.CalculateTaxes(this.items);
   
}
}

Second thought; when I see this


AddItem(item, taxService, discountService)   


I start wondering what information returned by the taxService or the discountService can cause the command (or a subsequent command) to fail?  What business invariant is the Cart supposed to protect that it needs a calculation of taxes and discounts made the last time an item was added.

On the other hand, these calls make a lot of sense to me:

cart.AddItem(item);
discountService
.ApplyDiscounts(cart);
taxService
.CalculateTaxes(cart);

because they confirm my bias that tax and discount calculations belong outside the AddItem transaction, as part of the projection of the Cart.  I'm imagining something along the lines of "Here are the items currently in your cart, with an estimate of the taxes and discounts that would apply if you were to place the order right now".  A useful question to explore might be: if I refresh my view of the cart 12 hours after I last submitted an item, should I see discounts calculated 12 hours ago, or should I see the discounts calculated now?

On the other hand, if you are thinking that all three of those calls run in the same transaction, then I'm really confused.  The spelling looks like commands being applied to the domain services (which is inherently weird); if they are changing the state of the cart, then that makes me suspect the cart isn't guarding its own invariants very well; if they are querying the cart and then invoking another cart command, then I'm confused that the Cart is supporting queries (previous note) and that all three commands - which always need to be grouped together, per your comment - haven't been rolled into a single command.

"Ceci n'est pas une pipe."  The running total sounds like a requirement on the projection of the cart; are you sure that it needs to be supported on the command side?

Michael Clark

unread,
Jan 6, 2016, 12:39:40 AM1/6/16
to DDD/CQRS
The thing that troubles me about this is that between the time that cart.AddItem is called, and taxService.CalculateTaxes(cart) is called, the ShoppingCart isn't in fully consistent state.  The tax amount is incorrect for the current set of items in the cart, which would mean an invariant of the ShoppingCart aggregate has been violated.  It also breaks the encapsulation of what the operation truly is, and increases the likelihood that another developer will use this ShoppingCart incorrectly in the future (e.g "Wait, I have to apply discounts and THEN calculate taxes?  I thought we apply discounts to the total after tax!")
 
It also means that 

Ben Kloosterman

unread,
Jan 6, 2016, 12:40:39 AM1/6/16
to ddd...@googlegroups.com
Yep its a query but note he is using DDD not cqrs ?

Ben

Ben Kloosterman

unread,
Jan 6, 2016, 12:51:39 AM1/6/16
to ddd...@googlegroups.com

All these considerations are IMHO secondary to keeping an organized domain - if its not trivial in size   . In terms of other programmers all this will be behind an add to cart method to thats not an issue. 

 "which would mean an invariant of the ShoppingCart aggregate has been violated."

This is important and suggest  a failing in the business model ,  there are 2 cart entities  one with items the other with taxes and discounts . This also then makes it clear to your developers . 

"cart.AddItem(item);
// discounts and taxes need a complete picture of cart without other discounts and taxes
var discounts = discountService
.ApplyDiscounts(cart);
var taxes = taxService
.CalculateTaxes(cart);"
cartwithtaxes. ApplyChange  ( cart  , discounts , taxes); 

cart is used normally , cartwithtaxes is used for checkout. 


Regards,

Ben




Michael Clark

unread,
Jan 6, 2016, 12:56:46 AM1/6/16
to DDD/CQRS
Strictly speaking I'm not using CQRS, but I am trying to model my aggregates with commands and queries so that I can eventually break my read concerns and write concerns into separate models.  Unfortunately that means for now I don't have the luxury of putting that logic of calculating taxes in an event handler on the read side.

The point you make about refreshing the cart is a good one, but I'm not sure that this would be any different in CQRS either right?  If my read model was relying on domain events to update itself, wouldn't the last event published (and subsequent read model update) be the ItemAdded from 12 hours ago?

Michael Clark

unread,
Jan 6, 2016, 1:25:11 AM1/6/16
to DDD/CQRS
"In terms of other programmers all this will be behind an add to cart method to thats not an issue."
Which AddToCart method?  I'm having keeping all of this behind ShoppingCart.AddToCart (AddItem, or whatever else).  If you mean it will be behind some additional Facade layer (e.g. ShoppingCartService.AddToCart) then that's really just pushing the problem up one layer, and it's the ShoppingCartService not ShoppingCart that will actually model the business process.  The only difference I see is that because one is called a "Service" not "Entity" it's becomes acceptable to inject it with domain services.

Jimmy Bogard wrote an article about this which I'm modelling some of these ideas after:


He's also against constructor injection (and probably method injection to some extent) but from what I can tell he's REALLY against forcing people to remember a particular sequence of calls to keep a model consistent.  I tend to agree with his reasoning here.

The idea about CartWithTaxes is interesting though, reminds of an example Scott Wlaschin used to demonstrate modelling in F#.  It's all about "making illegal states unrepresentable" which is totally my goal here.


"This is important and suggest  a failing in the business model"
I would totally agree that there something flawed with how I'm thinking about the problem or modelling the solution, just not sure exactly what it is yet.  

However, your insights are greatly appreciated!

Mike

Kijana Woodard

unread,
Jan 6, 2016, 1:30:52 AM1/6/16
to ddd...@googlegroups.com
The taxes are only estimates until you checkout. If you have a two year old cart, you pay current taxes.

Discounts are similar. They aren't really part of the cart, just overlays based on what's in the cart. The user added certain things to the cart. All else is projected until checkout.

Consider it this way, how could you do what if testing on various promotions if the promotions themselves cluttered the model?

Say you have a buy one get one free promotion. You add one, cart appears to have 2. Now you remove the item. The get one free should disappear too. If it's projected, it just works. If another item was added to the cart, how do you deterministically untangle the state?

From: Michael Clark
Sent: ‎1/‎5/‎2016 11:56 PM
To: DDD/CQRS
Subject: Re: [DDD/CQRS] Injecting Domain Services into Entity Methods vsConstructors

Kijana Woodard

unread,
Jan 6, 2016, 9:01:51 AM1/6/16
to ddd...@googlegroups.com
I guess the root of the tension is to try and fit all behavior having to do with the subject in a single class Order or Cart.

Where does it stop?
Inject IShippingCharges Calculator?
IPackageTracking?
ICreditCardProcessor?

The definition of anemic must mean something other than "does everything".
Somehow we have to find a line of "does enough" and then use other models to help shoulder the burden. 

Michael Clark

unread,
Jan 6, 2016, 1:08:13 PM1/6/16
to DDD/CQRS
So I think I'm starting to understand what everyone's saying about taxes.  Taxes aren't a part of a ShoppingCart, they're a part of the Order.  My original model had the reverse problem.  You don't AddItems to the Order, you add them to the ShoppingCart.  You're right that the tension is in making the models do too much.  Breaking the responsibility up between these two models makes things a little easier for me to digest.

So my takeaways from this would be:

You AddItems to a ShoppingCart
You create an Order from a ShoppingCart, and part of the process of building the Order would be to apply Taxes, Fees, Promotional Items, etc.
Once created, the line items and totals from an Order could be immutable.  I may be able to modify limited state on the Order through commands like CancelOrder or UpdatePaymentInformation.  I could perhaps revise an existing order by using it to create a new ShoppingCart, or reopening an old one.  Whatever makes the most sense given my current requirements.

The requirement to display a running total, or accurate promotional items is really more of an Application concern, not of a model.  So I can leverage domain services like ITaxService within my Application Services (the facade Ben referred to), to give an up to date picture of what my Order WOULD look like based on the current items in my cart (the projection aspect you and Danil refer to).  Not displaying the correct tax total would be more of a bug in my Application, but the domain model itself would not be in an invalid state.

Hopefully that is a reasonably accurate interpretation of everyone's insight.  This was incredibly helpful, I can already feel some of that tension starting to unwind!

Mike

Danil Suits

unread,
Jan 6, 2016, 2:34:40 PM1/6/16
to DDD/CQRS
"The requirement to display a running total, or accurate promotional items is really more of an Application concern, not of a model."

Oh wow.  I really like that way of expressing it.
Reply all
Reply to author
Forward
0 new messages