Domain messages – Exploiting their full potential
Software development can get complex. The requirements can be more or less specific and they tend to change as the project advance. Depending on the application itself, some changes are related to domain logic and others are domain logic side-effects.
While we are mainly focused on the domain (we spend extra time making the domain logic flexible and easy to change), side effects can get way less attention, as they are often added long after the domain logic is completed and delivered. After a few months of orphaned module, the product owner walks in and says:
“Let’s just send an email when we update this field, to let our coworkers know we are up to no good… Aaaand we need it by the end of the day.”
For projects that don’t have a test suite in place, any change, no matter how trivial, will make us feel hesitant about making it, especially if we aren’t intimate with the code base.
Best possible scenario is not having to change a single line of code, just add new functionality. If we create a new class and just place the right method call at the right place, we have changed the existing code in two places method call and class injection.
By Decorating the class, we have to change the factory or dependency injection container/configuration. If we visit a class, we still need to instruct the visit and change some code.
While not having to change the code to add new functionality sounds pretty unrealistic, it is very easily achievable. We just make the domain logic tell the rest of the system a relevant event occurred. Make all domain services depend on Message system and domain message handlers handle specific domain messages. In statically typed languages we can even enforce single responsibility principle on domain message handlers to handle one type of domain message.
To help you visualize what is going on, here is a simple diagram:
The obvious benefit is that the business logic / domain logic is absolutely obliviant about other things that it causes. But, trust me, there are more important benefits that have far surpassed my expectations.
First and most important one was that new team members felt confident enough to add a feature to a decent size legacy code base. All of them did it quickly and without breaking anything. The fact that they could handle the task in no time, in a legacy code base, boosted their morale. Yes, you read that right.
Another benefit is decoupling domain logic from side-effects, so domain logica and business rules always stay clean. This becomes important as the project matures. Business logic classes have little dependencies. They no longer depend on domain irrelevant things. They aren’t really interested if, why and how something else should happen.
Side effects to domain logic are now separated. And each does one and one thing alone. In a legacy project, separating side-effect from domain logic is part of refactoring. It’s baby steps, but the code looks cleaner and the team feels confident they can contain the beast.
Suitable for NoSQL database redundant data consistency
The message system was especially useful for handling redundant data in MongoDB. Again, domain logic was oblivious about redundant data. It should not care about it. We can very easily achieve immediate, eventual or no data consistency at all. All of those are domain message listeners responsibility and because they handle one message for one purpose we can change the behavior in one place (by providing a new listener), without affecting the rest of the system.
Other useful scenarios
On domain object creation, you might want to create extra objects, such as some defaults user can change later. For example, on account creation, create his company with the account name, email etc. When deleting a domain object, you can store the deleted object in some archive, provide undo operation or delete redundant data, while keeping the domain logic clean.
If you care about business intelligence, you might not want ever to delete or update data, as you are losing business intelligence, so you want to store it in a separate database and keep the main database always clean and up to date, domain messages make it easy.
Side effects don’t even have to reside in our application. Maybe we need to inform third party about account changes, such as billing address, emails etc. In grassroot development, we don’t need to worry should we create a separate microservice for this side-effect or not. We can create it in the monolithic application and move it when our app grows, as the only caller to that service will be a message listener; it’s not coupled with domain logic.
The real issue is what domain message to broadcast and when. In case of doubt, calculate the cyclomatic complexity and you got the domain message count for a certain procedure. Collections can be broadcasted altogether. Some sensitive information, such as passwords, credit card numbers, PINs, etc. should never leave the domain service as a domain message. It can pose you more trouble than gain, so be extra careful. Usually, you want to know when something has been created, updated or deleted.
There are numerous scenarios where domain messages can be found useful, but in the end – no method will ever fit all needs. We need to be aware of the choices we have, so that we can take the one most suited for problem at hand.