Hacker News new | ask | show | jobs
by barumi 2053 days ago
> The biggest drawback I've seen to CQRS - particularly in an event driven architecture - has been increased complexity, usually in exchange for increased scalability.

I fail to see how CQRS is a factor in increased complexity. After all, CQRS boils down to separating the interfaces for queries/reads/immutable operations and commands/writes/mutable operations. Unless you're bolting on unrelated concepts, like event sourcing, then CQRS is not a significant source of increased complexity in non-trivial applications.

Can you shine some light on which aspects led to higher complexity in your use of CQRS?

3 comments

I think the issue really is that when people talk about CQRS they are almost always talking about event sourcing + CQRS, and that's where the complexity lies.

I mean, look at GraphQL, which essentially makes it trivial to implement "plain" CQRS because there are always explicitly separate operations for mutations and queries, and it's easy to have separate object definitions for these operations (this is in direct contrast to many REST architectures, where your verbs - GET, PUT, POST, DELETE, etc. - are fundamentally operating on the same objects). However, underlying in the actual DB there is just a single representation.

But I think when people talk about CQRS these days they are really talking about the underlying data being different (e.g. a log of write operations vs a repository of queryable objects), and that usually implies something like event sourcing, and with that model you have a ton of complexity involved in keeping the different "sides" in sync, and since many update operations also involve queries (i.e. only do this update if something else is true about another object) you get into transactional difficulties as well.

Correct, this is what I meant. Even though they're technically different concepts, I've found in practice they're almost always coupled together.

FWIW, these days I usually do go with GraphQL over REST in my APIs, partially due to some of the advantages you get from separating the queries and mutations. But like you said, I prefer to ultimately store the info in a DB in one consistent format unless it's absolutely unavoidable. Sometimes it is unavoidable, but like the article mentions you can frequently get away with having a "Reporting Database" separate to your "Primary data" database.

Once you start spreading your data across multiple databases and storing it in different structures managed by different apps with different APIs, suddenly getting to even eventual consistency can quickly become a non-trivial problem, which itself is usually solved or mitigated by adding more complexity.

Sometimes this complexity really is required because of incredibly large scale, large/numerous engineering teams working on one project, or unique business requirements - but my original point was that for most cases, particularly new products/projects that aren't going to have 10M+ daily users from launch, you should keep it simple until you know for a fact that it needs the complexity of a full CQRS + event sourcing system.

> Even though they're technically different concepts, I've found in practice they're almost always coupled together.

Not really. Event sourcing is typically implemented with CQRS, mainly due to how ES relies on streams of command messages and as CAP-related requirements already require mutable and immutable commands to be processed differently. However, CQRS has zero requirements other than segregating interfaces.

Hell, in languages like C++ you already get CQRS out-of the-box by following const-correctness.

One obvious drawback is the amount of boilerplate code and multiple files. Although the essence may be no more complex, the amount of "noise" and fragmentation across files can increase the mental load.
Yep. Additionally to the separation you have the idea of domain specific commands instead of just writing objects/updating fields which lends itself well to custom side effect logic and constraints/sanity checks.