5
votes

CQRS states: command should not query read side.

Ok. Let's take following example:

The user needs to create orders with order lines, each order line contains product_id, price, quantity.

It sends requests to the server with order information and the list of order lines.

The server (command handler) should not trust the client and needs to validate if provided products (product_ids) exist (otherwise, there will be a lot of garbage).

Since command handler is not allowed to query read side, it should somehow validate this information on the write side.

What we have on the write side: Repositories. In terms of DDD, repositories operate only with Aggregate Roots, the repository can only GET BY ID, and SAVE.

In this case, the only option is to load all product aggregates, one by one (repository has only GET BY ID method).

Note: Event sourcing is used as a persistence, so it would be problematic and not efficient to load multiple aggregates at once to avoid multiple requests to the repository).

What is the best solution for this case?

P.S.: One solution is to redesign UI (more like task based UI), e.g.: User first creates order (with general info), then adds products one by one (each addition separate http request), but still I need to support bulk operations (api for third party applications as an example).

1
even better: not only do you need to validate that products exist but you also need to make sure that product was not deleted from DB between moments of you validation in app code and moment you save it to DB afterwards (after successful validation) :-). That's one of the reasons why its a question if CQRS is a good thing to do for this. Also, there are alot more considerations to address the question properly. One being: should your products adding be atomic or should you add those that exist and somehow generate errors for those that don't? Your DB is probably the best bet here. - dee zg
If you want atomic operation, put everything in DB transaction, have ItemProductId in ORders table to be ForeignKey from Products table and in your code check if there was ForeignKeyViolation exception (implementation depends on your app & DB platform). - dee zg
Why don´t you think about the object encapsulating the line items as a Customer Request. Once you get the CustomerRequest from clients you can create an order and add its lines based on the request always using the product repository to get the existing product. - Sebastian Oliveri
@SebastianOliveri, I've mentioned in the question, that this is not a good option because then I would need to load multiple aggregate roots, (multiple requests to the repository to check each provided product). Even more, the repository is implemented as Event Sourcing, so it would be not quite efficient to load many ARs. (Hm, maybe not as inefficient?) - Teimuraz
@Teimuraz I also facing this. But why server shouldn't trust the client. I think CQRS's client means our application server isn't it. Why shouldn't we validate things before access to command handler. - b.ben

1 Answers

11
votes

The short answer: pass a domain service (see Evans, chapter 5) to the aggregate along with the other command arguments.

CQRS states: command should not query read side.

That's not an absolute -- there are trade offs involved when you include a query in your command handler; that doesn't mean that you cannot do it.

In , we have the concept of a domain service, which is a stateless mechanism by which the aggregate can learn information from data outside of its own consistency boundary.

So you can define a service that validates whether or not a product exists, and pass that service to the aggregate as an argument when you add the item. The work of computing whether the product exists would be abstracted behind the service interface.

But what you need to keep in mind is this: products, presumably, are defined outside of the order aggregate. That means that they can be changing concurrently with your check to verify the product_id. From the point of view of correctness, there's no real difference between checking the validity of the product_id in the aggregate, or in the application's command handler, or in the client code. In all three places, the product state that you are validating against can be stale.

Udi Dahan shared an interest observation years ago

A microsecond difference in timing shouldn’t make a difference to core business behaviors.

If the client has validated the data one hundred milliseconds ago when composing the command, and the data was valid them, what should the behavior of the aggregate be?

Think about a command to add a product that is composed concurrently with an order of that same product - should the correctness of the system, from a business perspective, depend on the order that those two commands happen to arrive?

Another thing to keep in mind is that, by introducing this check into your aggregate, you are coupling the ability to change the aggregate to the availability of the domain service. What is supposed to happen if the domain service can't reach the data it needs (because the read model is down, or whatever). Does it block? throw an exception? make a guess? Does this choice ripple back into the design of the aggregate, and so on.