1

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?

  • S == segregation. – Ewan Dec 3 '18 at 19:57
  • Of course you can query your database in command handlers, however you should query your write side, not your read side. I.e. calling your CustomerRepository#findById(id: CustomerId): Customer is completely fine. Whether you should also be able to query your read side is for much longer discussion and depends on many variables, therefore I'm not going to go into the detail here. – Andy Dec 3 '18 at 20:11
  • @Ewan exactly. So now my confusion is how do I get that data? I could have a request with 20+ parameters, but this seems odd. – keelerjr12 Dec 3 '18 at 20:12
  • @Andy I'm not using repositories. Trying to move to a vertical slice architecture like in Jimmy Bogard's Vertical Slice Architecture – keelerjr12 Dec 3 '18 at 20:14
  • You're probably taking some concepts from the lecture too literally. You need some data access layer to pull aggregates from your database into the memory. The repository pattern is usually the one which is the most flexible and therefore recommended. Just because you're packaging by feature and not by layer does not mean you cannot have repositories either. Just create a customer repository and use that to fetch a customer instance using an attribute defined by you. – Andy Dec 3 '18 at 20:20
5

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!

  • This might be one of the best answers I have ever seen about DDD aggregates. But my understanding of DDD leads me to believe that Buyer, in this case, would be an AR and would be persisted. However, there is no Order repository which I thought would also be an AR. – keelerjr12 Dec 4 '18 at 21:40
  • I understand I most likely missed an AR and as you alluded to and also found at Udi Dahan's blog, you shouldn't just create an aggregate out of thin air. However, with minimal experience I would've called it something like var order = customer.PlaceOrder(products); orders.save(order); – keelerjr12 Dec 4 '18 at 21:46
  • @keelerjr12 You aren't wrong here. See my edit above. – king-side-slide Dec 5 '18 at 16:33
1

Is it okay to perform queries to get the data to build the aggregate roots I need to then pass?

Are you worried about data races?

If you aren't worried about a data race, then you can safely use a stale copy of the real data, and CQRS really doesn't care all that much where the stale data comes from.

On the other hand, if a data race would be a problem, then you probably need to stop and rethink things.

In this specific case, you are probably fine to use a query; I guess that because you seem to be looking up customer information that you don't control -- what you are querying is information that is controlled by the real world. So your data might already be wrong (the customer changed names, but you haven't gotten a notification yet).

  • No not really worried about a data race. I guess the issue would be re-hydrating the aggregates with the data I need. Like I said Order requires a List of Items and a Customer aggregate. So I need all the data to pass into the Product and Customer constructors before I pass those objects into the Order constructor (i.e. Order.Create(new Customer(id, name, email, shipping address), items). – keelerjr12 Dec 3 '18 at 21:50
  • Why are you passing aggregates to the constructor of another aggregate? That is really suspicious. – VoiceOfUnreason Dec 3 '18 at 21:52
  • Isn't that good OO? Pass a Customer object... Order.Create(Customer, items) vs Order.Create(CustomerId, FirstName, LastName, Email, items).. – keelerjr12 Dec 3 '18 at 21:53
  • And constructor was bad wording.. should be: pass aggregates into the methods of another aggregate e.g. create method inside Order. – keelerjr12 Dec 3 '18 at 22:07
  • It depends on what behaviors/capabilities your Order object needs to do its work. – VoiceOfUnreason Dec 3 '18 at 22:08

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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