3

I have a system using CQRS/ES, where:

  • The command handlers are actually implemented as an application service layer, which executes synchronously. I.e. events are persisted in the event store before the application service layer returns.
  • The read model is not required to be updated synchronously.

My implementation departs from typical CQRS/ES, in that:

  • the aggregates actually do expose some state via read-only properties;
  • the command handlers (application services) return the aggregate, and my web API is able to return updated state in its responses;

This allows the client to update its representation of the affected resources, without having to perform a follow up query immediately (which possibly would return data from the out-of-date read model, since read models can be updated asynchronously).

This approach appears to be frowned upon, given that most of the literature says that command handlers should not return domain data.

What are the drawbacks of this approach, and is it an acceptable way to avoid post-command queries, and dealing with the eventual consistency of the read model?

3 Answers 3

5

CQRS is a principle, not a dogma. Like all principles, it has its positive and negative aspects in its own right. Departing from "pure CQRS" could just mean that you can't meaningfully call it CQRS.

Quoting Martin Fowler:

The really valuable idea in this principle is that it's extremely handy if you can clearly separate methods that change state from those that don't. This is because you can use queries in many situations with much more confidence, introducing them anywhere, changing their order.

In other words, queries crafted this way are idempotent, and therefore provide the same benefits as any other idempotent or "pure" method. cf. functional programming.

Like all techniques, it has a cost:

Despite these benefits, you should be very cautious about using CQRS. Many information systems fit well with the notion of an information base that is updated in the same way that it's read, adding CQRS to such a system can add significant complexity. I've certainly seen cases where it's made a significant drag on productivity, adding an unwarranted amount of risk to the project, even in the hands of a capable team.

The questions you need to ask yourself are these: do you need these benefits or not? And are the benefits worth the additional cost?

Fowler discusses the benefits and pitfalls of CQRS in detail here and here.

3

This approach appears to be frowned upon, given that most of the literature says that command handlers should not return domain data.

There are a lot of conflicting ideas in the space, and you have to be a bit careful about which sets of ideas people are talking about.

CQRS - the idea that we can have two data models rather than just one -- takes its name from CQS, Command Query Separation, which is the brain child of Bertrand Meyer. Meyer's idea was that asking a question should not change the answer; from that idea, it follows that messages that change objects don't return answers.

But the context of Meyer's comments is sending messages within a process, which is to say it depends on reliable message delivery. When we get to distributed systems, sending messages via unreliable transport, then we presumably need some sort of acknowledgment/receipt to know that the message actually arrived at its destination.

If you are hosting your domain model in a service where you read command from a queue and apply updates to a book of record, then your "command handler" doesn't need to return domain data because the queue isn't a thing that cares, beyond the fact that a given command in the queue has been processed and can be garbage collected.

On the other hand, if you are hosting your domain model in an application, it can make sense to use the local copy of the domain data to create a response.

You do, however, need to keep in mind that the data models supporting read have not been updated yet - there's some possibility that you send a response to the client using this latest copy of the domain data, the client sends a query, and the query gets to the "read model" before the background processing manages to update it.

In other words, there is a sort of data race which, if not managed carefully, may give the client an inconsistent view of your system (costing you some confidence, perhaps, when a human user is involved).

This isn't a flaw, so much as it is an inevitable consequence of updating the query models independently of updating the command models. Which is to say, before applying CQRS you should already have analyzed your constraints and determined that such races are acceptable.

2

This allows the client to update its representation of the affected resources, without having to perform a follow up query immediately

The catch is in the implication here. What you're effectively saying is:

I have to fire one less request when I merge the two requests (i.e. the command and subsequent query) into one.

What you're saying is not wrong, but you are violating the core tenet of CQRS, which it is literally named after: Command Query Responsibility Segregation.

You are correct that merging these two requests saves your frontend a small bit of overhead performance related to the firing and waiting for a second web request (but we can definitely argue about whether this overhead is significant or negligible, which I'll get into in a bit).

If you're dealing with a slow network connection, this difference can be non-negligible. e.g. I've developed 3G mobile software where network requests were minimized as much as possible due to spotty connections.

However, CQRS isn't focused on optimizing the performance of the frontend, it's focused on maintainability and scalability of the backend. This will also benefit the frontend, but in an indirect way. CQRS allows for scalability of your read store (since it is then separated from your write store), thus lowering the overall time of your second request; instead of preventing you from having to fire that second request.


I do want to point out here that for connections with no strict limitations, the cost of performing a second call is negligible and not reasonably spotted by an end user. If there is a noticeable lag due to a second request being fired, on a good connection, that actually suggests that your system should be scaled up (as the requests aren't being handled in a reasonable time), which is what CQRS helps you with.
If this is the situation you find yourself in, then undoing the command/query segregation is effectively perpetuating your performance issues instead of improving them.


Do you have to use CQRS? Of course not. Just like any other principle or pattern, it exists to fix a particular problem. If the problem doesn't exist in your scenario (or is not considered a problem), then the principle/pattern is not needed.

But your CQRS "variation" is actually undoing what is essentially the first and only commandment of CQRS: separating your data operations from your data queries.

Does that mean you shouldn't do what you're doing? No, not necessarily. But I wouldn't call it CQRS anymore as it's quite the opposite.


which possibly would return data from the out-of-date read model, since read models can be updated asynchronously

This is a cart-before-horse situation. If you don't want to deal with the consequences of having your read store updated asynchronously, then don't asynchronously update your read store.

It sounds facetious but it really is as simple as that. Asynchronicity has its upsides and its downsides (just like everything), and if you don't want the downsides, then don't do it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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