today I want to make compendium of CQRS for very simple case.
So, at one of the projects I seen following way of implementing code:
and that way of implementing it was proudly named CQRS. To be honest I wasn't very agreeable on that part, so I decided to hear opinions of other people, not only mine understanding of what I read in book.
And I'd like to save one of the comments not only at stackoverflow, but also at my web site.
So, what's wrong with this approach?
Liskov Substitution Principal is one problem -- the WriteModel supports mutations (via commands); to inherit from the WriteModel as you do here implies that the ReadModel also supports commands. Which implies that the WriteModel is not the only way to mutate the state of the model. That's very bad. If reverse the hierarchy, then you have a ReadModel supporting queries (good), and then the WriteModel inheriting from it also supporting queries. That's bad, but not to such a degree. In other words, by doing this, you are losing separation of the two models. When you abandon CQRS like this, you are giving up the benefits the pattern affords. Let's imagine this exercise -- suppose you start with a model representation using Command Query Separation (aka: CQS). A trivial implementation might look like
So for an example like a model of a bank account, a naive model might look like:
Superficially, all that CQRS adds here is the refactoring of the commands and queries into two separate implementations (ie: the "write model" and the "read model").
This, by itself, doesn't buy you very much -- two separate interfaces instead of one gives you some assurance that the compiler can enforce that the read only representation of the account can't be used to change it. Greg Young's key insight at this point: because we have separated the two models, we no longer need to use the same representation of the account state in both cases. In the abstract form, it might look more like:
This is a really big deal, because WriteModel.State can now be optimized for writes, and ReadModel.State can be optimized for reads. You can have completely different representations, completely different persistence strategies, even different skill levels of programmers working each model (programmers working with the read model may introduce bugs, but they can't possibly corrupt your client's data, because data modifications are restricted to the write model). But all of that goes away if you insist on the two models sharing a representation of state.