Tonight was the twelfth of SerialSeb’s alternative network beers events, and this evening we were lucky enough to have Udi Dahan joining us. Clearly a lot of people in the room were very interested in hearing what Udi had to say because for the first time in history (I think?) the usual altnet musical chairs didn’t happen – the speakers hardly changed for the entire hour. That said, I feel like tonight raised a whole lot of fascinating questions for me, and I’m thoroughly grateful to Udi for taking the time to share his insight, to Seb for organizing the whole thing, and to Tequila\ and Thoughtworks for beer and pizza – thanks!
Command/query separation is a relatively new concept to me, and I’m sure I’ve got the wrong end of the stick here, but I’m going to share my reflections on this evening anyway so I can look back on this in a year or so and laugh at myself. Feel free to laugh now if you’ve already got your head around all this.
Anyway. The underlying principle of CQS seems to be that reading data and changing data are actually fundamentally different operations in any business system, and that trying to use the same architectural style for both of these operations can lead to all sorts of chaos.
It also seems pretty obvious that CQS is a topic with a lot of potential “false peaks”. Maybe you’ve refactored your Customer object to use a ChangeName() method instead of exposing property setters for Forenames and Surname. Maybe you’ve exposed a bunch of denormalized data based on optimised stored procedures for your common query scenarios, and you’re still using a rich domain model to do your inserts and updates. In each case, you probably think you’re doing command/query separation – but there’s more to it than that. Until tonight, I thought CQS just meant having some special methods on your data access code for returning big lists of stuff in a hurry. Now, I’m pretty sure I don’t really know what it is at all.
A couple of great highlights from Udi’s contribution to the discussion tonight:
- Users are always working with stale data. The information on their screen could potentially be stale by the time they actually see it. In any multi-user system, people are always making decisions and requests based on stale data. (“This is obvious, it’s physics – you can’t fight it. Well, you can, but you’ll lose”)
- Separating queries from commands allows the commands to model the user’s intention more clearly – which in turn allows the software to deal gracefully with conflicts and failures (“Sorry, room 133 is taken – would you like room 155?”), where a more granular system might just throw an exception because the data is no longer valid. (“Booking failed – not in the database!”)
- The reason we create domain models is really just that we need somewhere to store all our complex business rules, but it’s easy for elements of business rules to leak into the controllers or presentation layers when we’re manipulating domain objects directly.
- The ideal CQS approach is that every business operation involves exactly three things:
- Find a domain entity
- Execute one method on that entity
- Commit the transaction.
- With this approach, it’s impossible for any business logic to ‘leak’ into the presentation or controller layers – because they’re not making any decisions. Every business operation, complete with all the validation and processing and rules associated with that operation, has to be exposed as a single entry point to the domain model.
- The domain entity that exposes the method will probably behave as an aggregate root for the purpose of that operation – but different entities will act as aggregate roots for different operations. Again, this was a bit of an eye-opener for me; talking about DDD gave me the impression that an aggregate root was a fixture of your business model, not something you could chop and change based on what makes sense for a particular operation.
Finally, an analogy of my own that came to me on the way home, that might help, or might be horribly naive and misguided, but which I rather like and which I’ll share here in the hope of provoking some conversation. ‘Traditional’ domain modelling is like home baking; your data store is a supermarket, where the various products on offer are your objects. They’re all there, laid out for you to search through and count and process. To do anything complicated – like making a soufflé – you need to acquire all the various objects required for that operation, then manipulate and combine them in all sorts of complicated ways to achieve the result you’re after. If anything goes wrong – you forget the butter, or you over-cook the eggs – boom! No soufflé for you. Transaction aborted.
CQS seems far more like eating at a fine restaurant. You don’t choose your meal from an array of component products; instead, you get given a menu – a read-only representation of the domain that’s optimised for rapid retrieval. Based on the information on the menu, you then execute a command – you tell the waiter what you’d like to eat – but the structure of that command expresses your intention far more explicitly than the complex series of interactions involved in doing it yourself. If the data that informed your decision is stale - say they’ve just run out of haddock -the command carries enough context that the waiter can offer you the sea bream instead, or perhaps the mackerel, and the entire dining transaction isn’t abandoned.
I guess the question is, do you want your users to feel like they’re making a soufflé, or dining in a Michelin-starred restaurant?