CQRS is not a top-level architecture pattern. Use it for components under stress (or if you need to expose a wide range of exposition models), but don’t make it a by default pattern for your whole platform / Information System
My first CQRS experience
The first time I built a CQRS system… it was in 2006. The term hadn’t be coined yet by Greg (that happened in 2010), but the practice was ubiquitous in finance with tons of high throughput platforms to build or to maintain.
Actually, the first time I’ve used this architecture style was not due to my young skills of tech architect, but rather on a technical constraint. The technology we were using to publish prices and other market data to our consumers (read side) was not really made to receive more than just subscriptions requests from end users. The throughputs and scalability capabilities we could have with that tech were great, but it wasn’t made to gather commands from consumers.
Hence we had to use another tech/channel in order to receive configuration change commands for our system (a pricing service). And because we had scalability and affinity constraints, I’ve ended up to split responsibilities of our nodes dealing with commands (a specific write cluster) from other ones dealing with queries/subscriptions computing the prices on demand and pushing them and following updates to our consumers (the read cluster).
Without even knowing it, it was my first Command & Query Responsibility Segregation experience.
But the young technical architect that I was at the time learned a few things along the way from that experience.
Some observations over the years
- Since then, whenever I need to scale and deliver great throughput figures, I always consider CQRS.
- Every time I need to provide a wide range of exposition models for different kind of consumers, I also consider CQRS.
- Last but not least. When I’m doing Event Sourcing, I usually end up with CQRS too.
Did I told you that I wasn’t the kind of people picking CQRS as default style when they say they want to do Domain Driven Design (DDD)?
Even if it is hard, I’m trying to avoid Cargo-cult as much as possible for years, forcing myself to explain why I choose this pattern or that technology.
I’m really against golden hammers in general.
And try to avoid embracing this mindset as much as possible.
Year after year, I also regularly observed that CQRS was conflated with event sourcing for lots of people. It was the main reason why I wanted to make this talk in 2016 to the MS experiences days in Paris: « CQRS without event sourcing.» Slides: https://www.slideshare.net/ThomasPierrain/cqrs-without-event-sourcing
During this session I’ve made a few recommendations, including this one:
« CQRS is not a top-level architecture pattern. Use it for components under stress, but don’t make it a by default pattern for your whole platform / Information System »
I think that the first time I heard this take was from twitter Greg YOUNG. But it did vibrate with the strong and opinionated « there is no silver bullet » part in me.
Retrospectively, I realize that I didn’t really get why it could be a bad idea at that time.
And then a few years after, I also realized that publishing too many public/integration events was also a big mistake.
Even with events, encapsulation is key
Indeed, you end-up violating the encapsulation of your app/API/Service. One should really consider every publicly published event (i.e. integration events between apps/API/Service) as tiny contracts.
Once published, you will hardly get the chance to change them afterwards (due to the pressure of your consumers).
Yet another mistake ;-)
And we weren’t the only ones making this mistake at first.Indeed, this has been a major warning communicated by the Event Driven Architecture (EDA) or #DDDesign communities back in the days.
Publishing too many kind of integration events is the best way to end-up with a distributed monolith. And trust me, you will regret it. Very much.
When doing CQRS badly, there is a risk to publicly expose too many of your events outside of your own app/service/api boundary.
One should avoid this at all cost.
The problem with passion
Recently, I’ve discussed with people that were so passionate about CQRS that they crossed that red line without even realizing it.
Instead of them building specific read-side models and APIs for their external consumers (i.e. belonging to other Bounded Contexts-BCs) from their CQRS app, they wanted to publicly share a massive part of their CQRS private models & events with other teams and BCs.
A kind of CQRS “à la carte” allowing others to build their own stuff from other's events, but across plenty of teams. A kind of “my CQRS is your CQRS”. Like if CQRS was a top-level architecture pattern…
Their idea was to let external consumers (to their app/service/api) build their own read projections from our events. To do so, they needed to publish a wide spectrum of their event types as public/integration ones on a common message broker so that other teams could clone all their relevant data within the events.
People thinking that “because our coupling is made with events makes it less painful” make a mistake.
One can easily spread the distributed monolith syndrome without even realizing it.
The pain of not being able to refactor & align your code with your domain because you have leaked your initial naive vision towards too many consumers a daily source of frustration.
Encapsulation in OOP was not chosen by chance. The same applies at the cross-service architectural level. This is also why I have always appreciated the service-oriented approach (now API): it allows you to expose only what others need. No more no less.
When I understood what they had in mind and tried to achieve, I realized that the warning made by Greg and other was far beyond the message I initially got of “there is no silver bullet” or “you don’t have to systematically pay this accidental complexity for everything”.
The reasons behind are far more important and the risks higher. If you don’t want to build a distributed monolith please read carefully this warning:
« CQRS is not a top-level architecture pattern. Use it for components under stress (or if you need to expose a wide range of exposition models), but don’t make it a by default pattern for your whole platform / Information System »
And don’t let others build their own read projections from your own internal implementation details.
This article was extracted from a twitter thread available here (with all the answers and comments too): https://twitter.com/tpierrain/status/1546039271676678144?s=21&t=trjFLLZ35oXsxpxXeW7XCw
Thanks for the interesting article, its a topic I’ve been thinking about for a while. I have a couple of questions:
ReplyDelete1 - Maybe I’m missing something but is creating a specific read model for an external consumer actually quite similar to providing some integration events (if these aren’t the same events used internally)? They are both public contracts you have to support, keep running etc. and can be built from the internal events. Maybe one benefit is that you have more control over the consumers but does it mean you might end up doing more work for them (to support change etc.)?
2 - When an external consumer needs to use a model based on data from several other systems how to handle that? In some cases you can orchestrate calls to multiple services and combine them but this might not always be the best performance wise or for example something like building up a search index?
Thanks again!