5

In a DDD implementation, I am trying to design my domain model and the way to properly interact with child entities of an Aggregate through the Aggregate root.

Imagine the following two classes in an Aggragate called "Basket".

class Basket
{
    string Id;
    int TotalPrice;
    List<Item> Items;
}

class Item
{
    string Id;
    string BasketId;
    int Price;
}

The Basket is the Aggregate root and the Item is a child entity, with one-to-many relationship.

Now, at some point the price for a specific Item might change, so I want to update the Price for this Item and also update the TotalPrice of the Basket that contains that item. I am following the rule that any "change" on a child entity should "pass" through the Aggregate root. So, imagine that Basket has a UpdateItemPrice(string itemId, int newPrice) method and Item has a UpdatePrice(int newPrice) method, that each does what their name describes. My question is, which of the following implementations is more proper?

Approach 1:

In class Basket:

void UpdateItemPrice(string itemId, int newPrice)
{
    var itemToUpdate = Items.Where(i => i.Id.Equals(itemId)).FirstOrDefault();

    if(itemToUpdate != null){
        var oldPrice = itemToUpdate.Price;
        itemToUpdate.UpdatePrice(newPrice);
        TotalPrice = TotalPrice - oldPrice + newPrice;
    }
}

And in class Item:

void UpdatePrice(int newPrice)
{
    Price = newPrice;
}

Approach 2:

In class Basket:

void UpdateItemPrice(string itemId, int newPrice)
{
    var itemToUpdate = Items.Where(i => i.Id.Equals(itemId)).FirstOrDefault();

    if(itemToUpdate != null){    
        itemToUpdate.UpdatePrice(newPrice);
    }
}

In class Item:

void UpdatePrice(int newPrice)
{
    var oldPrice = Price;
    Price = newPrice;
    if(Price != oldPrice)
    {
        // raise a ItemPriceChangedDomainEvent
    }
}

In the first approach, what is happening is obvious. In Basket.UpdateItemPrice method, we update the Item's price through the Item.UpdatePrice method, and then we immediately calculate the new TotalPrice of the Basket there.

In the second approach, "changes" between the entities are propagated in a more "eventual" way. In Basket.UpdateItemPrice method we also update the Item's price through Item.UpdatePrice method, but then inside there we also raise an ItemPriceChangedDomainEvent, and do no TotalPrice calculation in Basket.UpdateItemPrice. What would happen next is that an ItemChangedDomainEventHandler will be invoked, that will find the Basket that contains the Item which's Price has changed through the repository, and then call a CalculateTotalPrice() method on the Basket (note: Domain Event handler and Basket.CalculateTotalPrice() method are not shown for simplicity).

The first approach is much simpler and straight forward than the second one, especially for my case where I only want to calculate a new TotalPrice after the Item's Price update. But, the second approach might be more scalable and flexible for future extension of the application, if for a example besides calculating the new TotalPrice, I also wanted to check if the Item's new price exceeds a theshold and then also raise an ItemPromotedForDiscountDomainEvent, which when handled will apply also a discount on the Basket's TotalPrice.

So, after all this talking, could someone give me a thought on which of these two approaches is more suitable/appropriate for me to follow?

Thanks in advance.

2
  • 2nd seems too uncomvenient and cumbersome. Why that "event" trip to calculate something you can after each call to updateItemPrice?
    – Laiv
    Commented Jul 11, 2021 at 9:35
  • You are right, "event" communication between the same aggregate is more complex than just doing any "calculation" I want inside the aggregate root's method that perform the intial change to child entity. It seemed convenient at first because the initial "change" was concering the child entity (the Item) so I wanted the "side effects" of this change to bubble up, from bottom to top. That's why domain events looked fitting in this case for me. Plus that I also read that this is a possible use for domain events here: shorturl.at/ivEU9
    – thomas_p
    Commented Jul 11, 2021 at 23:07

2 Answers 2

8

So, after all this talking, could someone give me a thought on which of these two approaches is more suitable/appropriate for me to follow?

Ruth Malan wrote: Design is what we do when we want to get more of what we want than we would get by just doing it.

One implication is that you have to have a good sense for the "what you want" if you hope to achieve a good design.

With "objects" -- in the Java circa 2003 sense of the term -- a common want is information hiding. In other words, we want a design that minimizes the blast radius of a decision that we might change later.

That usually means that each object gets to update the information in its own data structures.

Here, that probably means something like:

void Basket::UpdateItemPrice(string itemId, int newPrice)
{
    var itemToUpdate = Items.Where(i => i.Id.Equals(itemId)).FirstOrDefault();

    if(itemToUpdate != null){    
        itemToUpdate.UpdatePrice(newPrice);
        
        Total = CalculateTotalPrice()
    }
}

or even

void Basket::UpdateItemPrice(string itemId, int newPrice)
{
    Items
        .Where(i => i.Id.Equals(itemId))
        .ForEach(
            itemToUpdate => itemToUpdate.UpdatePrice(newPrice)
        )

    Total = CalculateTotalPrice()
}

Minimize control flow complexity and "area under ifs", favoring consistent execution paths and times over "optimally" avoiding unnecessary work. -- John Carmack, 2007.

Another consideration in domain code: it should ultimately look like it was written by a domain expert. So we might even want the plumbing to be simpler

void Basket::UpdateItemPrice(string itemId, int newPrice)
{
    var item = find(itemId)
    item.UpdatePrice(newPrice)
    Total = CalculateTotalPrice()
}

// raise a ItemPriceChangedDomainEvent

You won't normally see this to communicate across entity boundaries within the same "aggregate". Remember, an aggregate is "a cluster of related objects that we treat as a unit for the purpose of data changes". If our entities are together part of the same unit, they really shouldn't need to communicate via "domain events".

3
  • Thank you very much for taking the time to read my question and reply. Indeed the code in my examples is not the "prettiest" since I am not quite familiar with C# or LINQ yet. Also encapsulating some logic in methods, like Find() in your example, is a good idea, since I can reuse it and it also embraces the DRY principle. FInally, I 've read that Domain Events are primarily used to propagate changes between different aggegates, but after reading this: shorturl.at/ivEU9 it seemed to me like suitable use of Domain Events as I wanted the changes in the aggregate to propagate from "botom to top".
    – thomas_p
    Commented Jul 10, 2021 at 4:03
  • To sum up, what I keep from your answer is that I should stick doing any "calculations" involving a chlild entity directly in the aggregate root's methods and use Domain Events mostly for cross-aggregare communication. Thank you once again.
    – thomas_p
    Commented Jul 10, 2021 at 4:03
  • Bubbling up events within an aggregate root is, in most of the cases, unnecessary complexity. Root has enough scope and knowledge to know what to do when children changes. One more drawback could be that eventual communication via events might end in race conditions. Something relatively easy to avoid with Observer pattern, which in my opinion is also unnecessary here.
    – Laiv
    Commented Jul 11, 2021 at 9:57
0

In some cases the design is governed by the technology stack. For example .NET WPF applications make heavy use of binding directly to properties of objects. With binding the user directly updates the object properties (via getters and setters) and there is no 'UpdateItemPrice' method. It also uses events which are raised whenever a property of any item in the graph changes so you might be 'pushed' to add such events to your objects even if you would normally not need them.

If you are building such an application the second approach is an 'easier' (and more idiomatic) design choice so you would go with that one. In general I would say that if you have some sort of 'generic' manipulation of your objects you should probably go with second design where the root detecting changes to its items via events and you update the items directly.

Not the answer you're looking for? Browse other questions tagged or ask your own question.