CQRS and transactions in DDD world
One day you may need to dispatch few commands and you want all of them to succeed or none. To put it other words you want an transaction in your shiny CQRS/DDD world. This sounds like common scenario when you try to integrate CRUD system with CQRS world. CRUD focuses on state while CQRS is all about single change. This might be quite challanging issue.
With DDD approach there is no such thing as transaction. Transaction boundary are defined by your aggregate itself. That is why you are not allowed to persist more than one aggregate within single process. This restriction allows you to scale easily while keeping your data consistent (eventually). This also allows to build kind of sharding where particular aggregate instance inhibits well known shard region (node) and route all its commands there.
First solution that comes up to mind is to simply use process manager. Just deliberately design whole process where one command is triggered after previous has succeeded based on catched events. In case of failulre you may want to run some compensating actions. Sounds nice. But there is a problem. There are circumstances in which you are literaly not able to revert changes you had introduced. Two simple examples:
1. You change name from X to Y but you have not idea if another command had not changed from Y to Z meanwile. So you cant change it back to X.
2. Sometimes exceeding some threshold (numeric property in your aggregate) results with particular actions. Even if you decrese the value back the actions has already been taken.
I think running commands within kind of transaction is still posible to some degree. Lets consider following approach:
1. Introduce SuccessiveCommands concept. This is modeled as simple container for other commands but with importat invariant. Each registered command must be addresed to same aggregate instance (same type and same idenitfier). Registering command you declare concrete event that proves the success (CreateProduct -> ProductCreated).
2. Add TransactionalInterface to your aggregate repository. When you begin an transaction repository will not save the aggregate. In fact it will cache it for future finds. Find will return cached aggregate. Reposiotory also will not publish any evants after save. Events will be gathered but not published. When you “commit transaction” you will persit data to your data access layer and in case of success publish all gatherd events to the wild.
3. Register handler for SuccesiveCommands (which is command itself). Set aggregate’s repository into transactional state. Run handler for each command from the container then “commit” changes.. Note that as transaction I mean behaviour described as step 2 not DAL level transaction. All must happened within single process to make sure your DI will use shared reposiotry instance.
4. Profit :)