Pergunta

I read the discussion Should CQRS Command perform a Query?

But I don't understand in which database the query is performed, because I have two distinct databases - one for reads (queries) and one for writes (commands).

If I need to take the customer info, I need to read from the Write Database? Is this way correct? I think yes because if I read from the Read Database, I need to inject the reference to ReadModel and ReadDatabase and I loose the SEGREGATION, right?

Foi útil?

Solução

If I need to take the customer info I need to read from the Write Database? It's a correct way?

Short answer: yes. But you're missing a few steps in your reasoning.

  1. Commands and queries are separated both for general cleanliness and the ability to independently scale the command or query sides.
  2. Independent scaling means that your commands (write) live on another physical machine than your queries (read).
  3. Different physical machines means the changes (made via commands) need to be synchronized from one (write) to the other (read)
  4. Such a synchronisation takes a non-zero amount of time, the read database is always behind on the write database (how much it is behind is a matter of specs).
  5. The main thing to take away here is that while the query database may be better suited to servicing large volumes of queries, the write database can actually be assumed to be more reliably correct since it is always the most up to date.

During a write action, you want the data that is most current. Therefore, you should get it from the write database, which is guaranteed to have the most current state of the data.

Just to take the train of thought to its final station:

  1. Separating your commands and queries entails accepting that there is lag between performing an action (write) and seeing its outcomes (read), because of the needed synchronization. That lag can be mitigated by upgrading the infrastructure and running more frequent replication, that is a cost-benefit analysis on its own.

Should CQRS Command perform a Query?

How you interpret "query" in that question very much changes the answer.

If by query you mean "getting data from a database", then the answer is "yes, you can perform a query (and you should run that query on the write database)".

But the "Query" is capitalized in that question, suggesting that it's referring to the CQRS Query (which inherently would connect to the read database), at which point the answer is "no, you should not run a Query (since it would fetch data from the read database)".

I think yes because if I read from Read Database I need to inject the reference to ReadModel and ReadDatabase and I lost the SEGREGATION, right?

Separate models or database contexts are not an inherent requirement of CQRS. It's perfectly possible to define a single context (think of a basic EF DbContext) and use it in both the command and query side of things. The only thing you'd really need to change is the connection string, not the shape of the model.

Note that this does mean you have no easy way to enforce only write or only read logic, but whether you need that check to keep your development in line is your call to make.

I'm not saying that you shouldn't separate your model. What you're doing is perfectly fine. What I'm trying to point out here that "needing to reference your read model" isn't a valid justification for your current question, as CQRS can exist without having a split model to begin with.

Outras dicas

The only reason a CQRS application should fetch some information (I don't call it a "Query", it would be misleading) in the middle of the command path, is that a use case needs to retrieve information from the outside world before to validate a command. The outisde world typically is another bounded context, so this means that, when you do that, you are calling a microservice, a 3rd party api, or even a method defined in a different module of you monolith

public void purchase(String orderId , String promocode) {
    boolean isValidPromocode = promocodeDomainService.check(promocode);

    commandGateway.send(new PurchaseOrderCommand(orderId , isValidPromocode));
}

This assumes that promocodes are managed from another service. After all, purchases are managed from sales/inventory/shimpent functions, while promocodes are a marketing concept, so it makes sense that are not part of the aggregate that performs the PurchaseOrderCommand

What happens behind the curtains is not our business. The domain service here is just an abstraction

Aside from this case, you aggregate must have everything it needs to validate its invariants

if (eventSourcing == false) In this case, the only statement that your (write) DB will process, would be that one which loads the aggregate. Before saving back your aggregate

public void purchase(String orderId , String promocode) {
    Order = repository.find(orderId);
    boolean isValidPromocode = promocodeDomainService.check(promocode);

    order.apply(new PurchaseOrderCommand(orderId , isValidPromocode));
    
    repository.save(order);
}

else The in-memory representation of the aggregate is updated as domain messages are published or retrived from the event store

In any case, on the query side, if you need some data that orginal aggregate doesn't own, you can still query external services or provide an aggregation layer in front of your projection

Licenciado em: CC-BY-SA com atribuição
scroll top