In this final part of my series (part I, II and III) on reversing the dependency from Domain Model to Data Access code found in many traditional layered code bases I will show how raising Domain Events when Domain Objects are updated can further decouple the Data Access component from the other components.
To recap the initial situation is this:
The two previous posts show how to reverse the dependency between Domain Model and Data Acces either by letting the Presentation Layer talk to the Data Access code or by invoking Dependency Inversion.
Leaking Data Access Concerns?
Reversing the depedency by letting the Presentation component depend directly on the Data Access component works, but leaves the responsibility of making the Data Access component save entities to the Presentaion component. E.g. in this snippet of Presentation Layer code there is a call to wishListRepository.Save(wishList);
This may seem innocent enough, but saving is not a presentation concern, which means presentation code should not be making that .Save call.
Reversing the depedency by Dependency Inversion also works and alleviates the Presentation code from calling .Save. On the other hand the Domain Model code has to make the Data Access code save entitites. Doing this without the compile time dependency pointing from Domain Model to Data Acces is achieved by introducing interfaces like this in the Domain Model:
Again this seems innocent enough, but what about the Save method? On the one hand it does not reveal anything in particular about the underlying Data Access code or database. On the other hand it does imply that WishList objects can be saved - presumably to a database of some kind. Furthermore the Domain Model has to decide when to call .Save, which is not really a Domain Model concern. The Domain Model should be responsible for the Domain logic. The 'what' and the 'how' of persistence is the responsiblity of the Data Access component. The 'when' of persistence is also the responsibility of the Data Access component in simple cases and of Infrastructure code in more complicated cases.
Domain Events Enable Further Decoupling
If the Presentation component is not to call .Save and the Domain Model is not to call .Save then what? -The Domain Model will raise events when Domain Objects are updated - as done at line 10 in this:
The actual saving of the WishList is done in an event handler, which can be something along the lines of - notice that this class is part of the Data Access component and uses the Domain Model:
Now neither the Domain Model or the Presentation component makes any decisions about what, how or when to persist. The Presentation layer only retrieves Domain Objects as needed and interacts with them. The Domain Model just implements the Domain Logic and raises events whenever there are state changes. It is the full responsibility of the Data Access component to handle the 'what', 'how', and 'when' of persistence.
It is not always good enough to simply persist state changes right away as Domain Events are raised. Sometimes a Unit Of Work is needed. In such cases the Domain Event handlers should not persist data directly, but rather interact with the Unit Of Work to record changes. Whether the Unit Of Work eventually ends up being commited or not is not the concern of the Domain Event handlers. They can rely on Infrastructure code to take care of persisting or discarding the changes recorded by the Unit of Work.
Implementing a simple mechanism for raising and handling Domain Events amounts to iterating over all Subscribers calling them one after another when a Domain Event is raised, and to register all Subscribers. That is having this:
and registering all Subscribers with that class in the Composistion Root. This can be done by explicitly listing all subscribers, by discovering them by scanning assemblies or in case the application uses a IoC/DI Container by asking the Container to resolve Subscribers.
Rasing Domain Events further decouples the Data Access component from the rest of the components. It frees other components of the responsibility of deciding what and when to save, and it allows both for simply saving immediately and for delegating to a Unit of Work.
Raising Domain Events also allows for similar deploupling of other components - e.g. integrations to third parties.