简体   繁体   中英

CQRS: Can the write model consume a read model?

When reading about CQRS it is often mentioned that the write model should not depend on any read model (assuming there is one write model and up to N read models). This makes a lot of sense, especially since read models usually only become eventually consistent with the write model. Also, we should be able to change or replace read models without breaking the write model.

However, read models might contain valuable information that is aggregated across many entities of the write model. These aggregations might even contain non-trivial business rules. One can easily imagine a business policy that evaluates a piece of information that a read model possesses, and in reaction to that changes one or many entities via the write model. But where should this policy be located/implemented? Isn't this critical business logic that tightly couples information coming from one particular read model with the write model?

When I want to implement said policy without coupling the write model to the read model, I can imagine the following strategy: Include a materialized view in the write model that gets updated synchronously whenever a relevant part of the involved entities changes (when using DDD, this could be done via domain events). However, this denormalizes the write model, and is effectively a special read model embedded in the write model itself.

I can imagine that DDD purists would say that such a policy should not exist, because it represents a business invariant/rule that encompasses multiple entities (aka aggregates). I could probably agree in theory, but in practice, I often encounter such requirements anyway.

Finally, my question is simply: How do you deal with requirements that change data in reaction to certain conditions whose evaluation requires a read model?

First, any write model which validates commands is a read model (because at some point validating a command requires a read), albeit one that is optimized for the purpose of validating commands. So I'm not sure where you're seeing that a write model shouldn't depend on a read model.

Second, a domain event is implicitly a command to the consumers of the event: "process/consider/incorporate this event", in which case a write model processor can subscribe to the events arising from a different write model: from the perspective of the subscribing write model, these are just commands.

Having read a lot about the topic and having thought hard about it myself, I attempt to answer my own question.

First, a clarification about the terms used. The write and read models themselves never have any dependency to one another. The corresponding command and query components might have instead. I will therefore call the entirety of the command component and its write model the command side , and the entirety of one particular query component and its read model a query side (of which there might be many).

So consider a command handler that is responsible for evaluating and executing a business policy. It takes a command DTO, validates it, loads part of the write model into memory, and applies changes to it in one atomic transaction. The question specifically was, whether this handler is allowed to query one of the query sides in order to inform its decision about what to do in the write model.

The answer would be a resounding NO . Here's why:

  • The command side would depend on one particular query side (it doesn't matter if you hide the dependency behind an interface – it is still there), so the query side cannot change independently.
  • Who actually guarantees that the command handler runs when it has to? The query side is certainly not the one responsible for it, and clients aren't either.
  • The command request is prolonged by performing a nested query request, which might be detrimental to the performance.

Instead, we can do the following:

  • Work with domain events raised by the write model, register a domain event handler in the command side that evaluates the policy. This way it is guaranteed that the policy will be executed whenever it has to be.
  • If the performance allows it, this domain event handler can simply load as much of the write model as it requires to evaluate the business condition. Don't prematurely optimize – maybe the entities are small and can easily be loaded into memory.
  • If the performance does not allow it, denormalize the write model and maintain the required statistics using domain events. No one says that the write model cannot itself contain query-oriented data. Being a write model simply says that it is a model designed to do writes , and this necessarily must include some means to read as well.
  • Finally, if applying the policy is not an integral part of the domain logic itself, but rather just a use case, consider putting the responsibility of calling it into a client or another microservice, where it is totally fine to first query one of our query sides, and afterwards calling our command side with the appropriate parameters.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM