Should CQRS Command Perform a Query?

cqrsdomain-driven-design

I have Customer, Order, and Product Aggregate Roots… When the order is created, it takes in a Customer and a List<Sales.Items>. It looks something like:

public class Order
{
    public static Order Create(Customer customer, List<Sales.Item> items)
    {
        // Creates the order
        return newOrder;
    }
}

Then using CQRS I've created OrderCreateHandler which looks something like:

public class OrderCreateCommandHandler : IRequestHandler<OrderCreateCommand>
{

    public OrderCreateCommandHandler(ECommerceContext db)
    {
        _db = db;
    }

    public async Task<Unit> Handle(OrderCreateCommand request, CancellationToken cancellationToken)
    {
        var customerResult = // Q) Is it okay to execute a CustomerQuery here?

        var customer = new Customer(customerResult.Id, customerResult.FirstName, customerResult.MiddleName,
            customerResult.LastName, customerResult.StreetAddress, customerResult.City, customerResult.State, "United States",
            customerResult.ZipCode);

         //  blah....

         order = Order.Create(customer, products);
         _db.Orders.Add(order);
     }
}

My question is in the command handler, is it okay to perform queries to get the data to build the aggregate roots I need to then pass? I don't ever store the aggregate root itself (just a reference if I need), but I don't want to pass in Ids and primitives everywhere, I want to follow SOLID OO and pass actual objects. Is this a violation of CQRS?

Best Answer

Let's start with a short review of the problem-space here. The fundamental benefit of adopting a CQRS pattern is to solve/simplify your problem domain by reducing the interleaving and leakage that begins to occur when utilizing the same model for your write-side as your read-side. Often, the tension that arises serves as a detriment to both. CQRS seeks to relieve this tension by separating (decoupling logically and possibly physically) the write-side and read-side of your system. With the above in mind it should be clear that neither your commands nor queries should be coupled to a logical entity from the other "side".

Given the above, we can now formulate a direct answer to your question: Yes, you can query your data store within a command handler provided the query is issued against your command model. Because your OrderCreateCommandHandler is part of the command model of your application, we want to avoid coupling it to any part of your read model. It's unclear whether or not this is case given the example code (although the name CustomerQuery does raise some suspicions).

More important than the answer above though is that... there is something else that feels fishy about the example you have provided. Can you feel that too?

What I see here is quite a bit of coupling. Your handler is retrieving a CustomerResult (VO?), then breaking down all of it's data into another entity's constructor (Customer), then passing the Customer to a factory method of yet another entity. We have quite a bit of "asking" happening here. That is, we are passing around a lot of data in way that creates coupling.

Furthermore, the command handler doesn't "read" in a very declarative fashion (which is what we want to strive for). What I mean is that it's kind of hard to "see" what's happening in your method because there is so much plumbing getting in the way. I think we can come up with a more cohesive/declarative solution.

Given that the general "flow" of a command handler can be broken down into three simple steps:

  1. Retrieve all data (domain model) necessary to carryout the use-case
  2. Coordinate the data to fulfill the use-case
  3. Persist the data

Let us see if we can come up with a simpler solution:

buyer = buyers.Find( cmd.CustomerId );

buyer.PlaceOrder( cmd.Products );

buyers.Save( buyer );

Ah ha! Much cleaner (3 simple steps). More importantly though, not only does the code above achieve your same goal, it does so without creating many dependencies between disparate objects as wells as functioning in a more declarative and encapsulated manner (we aren't "newing" anything or calling any factory methods)! Let's break this down piece by piece so we can understand "why" the above may be a better solution.

buyer = buyers.Find( cmd.CustomerId );

The first thing I've done is introduce a new concept: Buyer. In so doing this, I am partitioning your data vertically according to behavior. Let's let your Customer entity have responsibility for maintaining Customer information (FirstName, LastName, Email, etc.), and allow a Buyer to be responsible for making purchases. Because some Customer information needs to be recorded when a purchase is made, we will hydrate a Buyer with a "snapshot" of that data (and possibly other data).

buyer.PlaceOrder( cmd.Products );

Next we coordinate the purchase. The above method is where a new Order is created. An Order doesn't just appear out of nowhere right? Something must place it, so we model accordingly. What does this achieve? Well, the Buyer.PlaceOrder method provides a place in your domain to throw BuyerNotInGoodStanding, OrderExceedsBuyerSpendingLimit, or RepeatOrderDetected exceptions. By only creating an Order in the context of it's placement, we can enforce how an Order can come about. In your example, either your application-layer command handler or your Order factory method would have to be made responsible for enforcing each invariant. Neither is a good place for checking business rules. Additionally we now have a place to raise our OrderPlaced event (which will be necessary to keep your payment context decoupled), and also we can simplify your Order entity as it now only needs a scalar buyerId to keep reference to it's owner.

buyers.Save( buyer );

Pretty self-explanatory. A Buyer now contains all of the information you need to persist both an Order and a "snapshot" of Customer data. How you organize that data internally and take it apart for persistence is up to you (hint: A Buyer needn't be persisted at all, for example. Just the Order it contains).

EDIT

The example solution (if we can call it that) that I posted is one meant to get the "gears turning", and doesn't necessarily represent the best-possible solution to the problem at hand. That is, your problem. It is totally possible (even likely) that introducing the concept of a Buyer aggregate is over-engineering given that there had been no mention of any sort of rules regarding how an Order can be placed. For example:

customer = customers.Find( cmd.CustomerId );

order = customer.PlaceOrder( cmd.Products ); // raise OrderPlaced

orders.Save( order );

may be a totally valid approach! Just be sure to include all of the necessary information in the CustomerInformationSlip (your "snapshot") attached to the Order to allow it to enforce any invariant controlling how it can be modified. For example:

order.ChangeShippingAddress( cmd.Address ); // raise ShippingAddressChanged

The above may throw an OrderExceedsCustomerShippingDistance if each Customer has their own rules regarding how far you will ship to them given their account tier.

Let the rules dictate the design!