CQRS Command – Handling CQRS Commands with Multiple Aggregate Roots

cqrsdesign-patternsdomain-driven-design

I have have a business process that receives an order request which also includes full customer information.

In the cases where the external customer ID from that order request is not found in our DB, we need to process that piece of the "create order" command, before we can actually save the order for that customer.

In the cases where we do find a matching customer ID but the address record in the DB differs from the one in the order request, we need to insert it as a new address record for that customer id (many CustomerAddress to 1 Customer) – note that this address also gets saved as part of the order's shipping address value object, because… well… those change, but the order is point in time.

I originally designed an orders aggregate with my Order entity as the aggregate root (note that I have the Id property defined in a base Entity class, which the AggregateRoot abstract class also extends, but omitted here for brevity):

public class Order : AggregateRoot 
{
   //ctor ....

   public long CustomerId { get; }
   public Address ShippingAddress { get; } //value type

   private readonly List<OrderItems> _items = new List<OrderItems>();
   public IReadOnlyCollection<OrderItems> Items => _items.AsReadOnly();

   // more props and behavior methods here....
}

In a separate aggregate, named "customers', I have my Customer aggregate root and a CustomerAddress entity:

public class Customer : AggregateRoot
{
   public string ExternalId { get; }

   private readonly List<CustomerAddress> _addresses = new List<CustomerAddress>();
   public IReadOnlyCollection<CustomerAddress> Addresses => _addresses.AsReadOnly();
   // more props and behavior methods...
   public void AddNewAddress(string externalKey, Address addressInfo)
   {
       _addresses.Add(new CustomerAddress(externalKey, addressInfo, this));
   }
}
public class CustomerAddress : Entity
{
   //ctor ...

   public string ExternalAddressKey { get; }
   public Customer Customer { get; }
   public Address AddressInfo { get; }

   // more props and behavior methods...
}

There will be times when we'll manage (add new, and update) customer data all by itself via UI and not in the context of an order. However, about 50-60% of the time the customer info will actually arrive as part of the order request API and sent to the internal command handler and we need to check if it already exists as mentioned above.

I suspect that I may have my aggregate boundaries designed incorrectly, but I am not sure, as I keep going back and forth in my mind how to achieve the business requirement as stated above using proper CQRS + DDD patterns.

I am not sure if I should treat my command handler as a sort of process manager/app service, and if I do that I'd be creating multiple transactions (working w/ 2 different aggregates) in the same command, which seems to go against the tenets of DDD.

Would appreciate some guidance on this. Thanks!

Best Answer

A comment from Eric Evans DDD Book:

On the other hand, a feature that can transfer funds from one account to another is a domain SERVICE because it embeds significant business rules (crediting and debiting the appropriate accounts, for example) and because a “funds transfer” is a meaningful banking term. In this case, the SERVICE does not do much on its own; it would ask the two Account objects to do most of the work. But to put the “transfer” operation on the Account object would be awkward, because the operation involves two accounts and some global rules.

Evans, Eric.

Domain-Driven Design: Tackling Complexity in the Heart of Software (S.107).

Pearson Education. Kindle-Version

Sometimes it is just not possible to achieve immediate consistency with a single aggregate root and it is not desirable to implement a long-running process. Then it is perfectly fine to use a domain service to coordinate changes on several aggregate roots.

Related Topic