Man, I’m tired. I’m hoping the little cup of coffee I just had will give me one more short burst of energy.
Why am I so tired? Because the project I’m working on is so freakin’ awesome that I’m working night and day on it.
Really, I’m so excited about the architecture we’re using and the technology stack that I think this may be the most fun I’ve had (at work) in 8 years.
So what is all this great stuff, you ask. Well, glad you asked. We’re moving to an architecture called “Command / Query Responsibility Segregation” (CQRS). In an oversimplified nutshell, the idea is this: You break your system into 2 “channels”, one for Writes (aka, “Commands”) and one for Reads (aka, “Queries”). You have Domain Objects (and we’re following DDD), but they are Write-Only. That will sound very strange if you’ve never heard of CQRS. But it does a ton of good things for you.
So if your Domain Objects are write-only, then how do you populate a screen with existing data? Well, you use the Query “channel” for that.
When a write occurs, your Domain Objects do it. They apply all of the intelligent behavior and business logic that you’ve so carefully built into them. They won’t let the write occur if anything is wrong with it.
If the Domain Objects do decide to let the write occur, then they will also fire an event saying that something has happened (as in, “A payment has been taken” or “A new account has been created”).
These events are listened to by many possible “subscribers”, one of which is the “Query channel”. It then records or updates projections of the relevant state.
This means that you can represent the data that is your “true state” in many different ways (aka, various read-only projections). For example, you might write some data into a OLAP star schema. You might also make a separate projection that is tailored to “Customer Edit” screen. And some of that same data might be pushed into a projection tailored for your “Nightly Financial Report”. Since these representations are read-only, they can be denormalized and tailored to the needs of the task that is doing the reading.
Then, when someone comes to the “Customer Edit” screen, you do not use the Domain Object to populate that screen. Instead, you read from the Query channel, from the particular read-only projection that was written there for the “Customer Edit” screen.
You see, in a sense this data still comes from the Domain Objects … just not directly. The Domain Objects still control every bit of the data used by the system … but you don’t “call them” to get the info. They put the info somewhere, and you get it from there.
I’m not going to go into too much more detail right now. But in a nutshell, here are some of the main benefits:
1. Your Domain Model is free to emerge and evolve into the right way to express your complicated domain logic. It no longer is encumbered with the additional job of providing thousands of little pieces of data on demand. No other parts of your system even need dependencies on your Domain Objects, and they certainly do not place any “demands” on the shape of those Domain Objects. (It is this benefit, moreso than scalability, that led me in particular to CQRS).
2. The ability to produce many different representations of the state on the “Read channel” is surprisingly powerful. By that I mean that, even though it sounds powerful, it turns out to be surprisingly more powerful than it sounds at first. Your Domain Objects (and some other “helpers” on that end of the system) can custom-tailor specialized “summaries” and representations of the data … one each for the various “Read tasks” that you have. This means that all your “Read operations” get much simpler … there is very little or no “transformation” work to do, because it was already done for you when the events were fired from the “Command channel”. Granted, this is merely moving work around, because you used to do that “transformation” work during the Read, whereas now you do it as an indirect consequence of the Write. But there are significant advantages of doing that work “back there” as a consequence of the Write … a lot of that transformation work is quasi-business logic, and it is very nice and clean to do it back there. And it’s wonderful to not have to do it when rendering an Edit screen or showing a report.
3. Scalability. Duh. This is the most highly touted benefit and it is a big one. Now you can optimize your Write channel for writes, and your Read channel for Reads. You can have many instances of your Read database, which you load balance between. Most systems have many times more Reads than Writes … so now your “Write channel” won’t be burdened with serving all those Reads.
4. Real-time OLAP. (This sort of fits under # 2, but I feel it deserves its own bullet point). Star schemas are so fantastic for presenting information about what has happened in your business. So many businesses that would benefit greatly from them don’t have them. It’s often approached as a nice-to-have after the OLTP system itself is working. But CQRS let’s you have a real-time OLAP schema, merely because those events from the Command channel can be captured in an out-of-band manner and recorded into a star schema. I say “real-time” because, even though there will be a lag time in seconds between the Command channel write and the OLAP update, “seconds” is still very much real-time to the business world. Plus, you don’t have to justify the OLAP schema as a separate project. It can be the foundation of your Read channel, and therefore it is necessary (and powerful) for the system you are building right now. But you can also do bits and pieces, rather than the whole Data Warehouse enchilada.
So I’m fired up. A co-worked told me the other day that he couldn’t sleep the other night because he was so excited about it. I’m fired up, but I have been able to sleep at least :)
If you want to learn more about CQRS, here are some great links: