When I made my first project with CQRS my assumptions where:
Command are requests that something should happen. They may fail.
Events are notifications that something happened. They cannot fail.
The write model contains the business logic.
The project was all about a complex calculation for a kindergarten. If a child enters/leaves a certain group, changes one of many classifications or ages one year, several complex calculations result in incrementation or decrementation of several statistic numbers.
My first approach was, to have some events like ChildEnteredGroup, ChildLeftGroup, ChildAgesOneYear and let the projections handle the calculations and the updates of the numbers. But this would have put all this complex logic and calculations into the projections, while the write model would have been empty, only containing "command to event" converter methods (LetChildLeaveGroup command fires ChildLeftGroup Event and does nothing else).
This seems unnatural to me and I decided to put all this logic into the write model. So in the end the write model makes all computations and emits events like "ChildsWithXYIncremented, ChildsWithXYDecremented" and projections that only translates this events into crud operations for a database table containing the numbers.
So far so good (I thought).
Now the customer came back to me and told me, that the logic changed. The rules what counts and what not, the ranges and so on where slightly modified.
"No problem" I said. "I just have to change my projections and replay all events and we are done."
But I was wrong. Because the logic and rules are in the write model, and the events only tell about the results, I could not change the logic and rules in the projections and replay all events and get a different result. To achieve this, I would have to change the logic and rules in the write model, deleting all events (!) replaying all COMMANDS(!!) generating new events, causing my projections to create the new output.
I ended with a "RecalculateCommand" that generates compensating events (effectiffly taking each child out of the kindergarten and emit events following the old logic and rules and then reenter every child and emit events following the new logic and rules).
If I had followed my first approach, putting all the calculations and logic into the projections, having a write model that just translates commands into events (with a little bit of validation). It would have been much simpler. Just changing the logic and rules in the projections and replay all events. Ready.
But I refused to put complex logic into the projections because it felt wrong to me. It felt more correct to me to put it into the write model.
For the same reason I think that putting logic and calculacions into an event method is wrong. The calculation should be made in the command method, that then triggers an event with the result, and the event method makes the state changes to my write model (aggregate or entity). But here also it is not possible to change (or correct) the logic later, and after reloading the aggregate have teh new (corrected) results.
So it seems to me that perhaps it is not wrong to put some logic and calculations into the event methods and/or projections. Of course they are not allowed to fail. And it must be logic that I may want to change in the future without worrying about "loosing" the old results.
So what would have been the right way?
More in general what logic should go into the command method, the event method and into the projections?
Please enlight me. :-)
Best regards,
Holger