Most of the programmers quickly agree that naming is one of the hardest things they have to do as part of their Job.
As Phil Karlton puts it “There are only two hard things in Computer Science: cache invalidation and naming things.“
Naming is hard because it is important to get it right. It is one of the key UX for readers/consumers of your software components. There is plenty of material (books/blogs) written on this topic. I have also shared my thoughts in another post.
Besides other challenges, developers of Event-Driven systems are suffering from this pain at another level. While building an event-driven system, generally we find it difficult to name an event. Often in this situation, if the name of the event you are raising does not resonate the business event then you should be alarmed and reassess the event.
“This happens when the source system expects the recipient to carry out an action, and ought to use a command message to show that intention, but styles the message as an event instead.”
Before digging deeper, lets define commands and events.
Commands are directed by 1 or more systems towards a single system to processes these commands. So when you send a command, it results in a success or failure. The calling system may or may not care about the outcome.
Events are the facts stated by the source system without targeting any system. Events are raised by one system and consumed by one or more systems. System raising the event does not know about the subscribers of that event.
So what is this trap that Martin Fowler is warning about and why should we care about “command versus event”? The easiest example to compare can be a system where several bounded contexts want to correspond with the user via email/SMS.
- Sales System wants to notify users of the new addition to the inventory.
- Dispatch System wants to notify users about dispatching of the product with postage tracking information.
- Correspondence system takes care of talking to the infrastructure layer as well as providing additional responsibilities of notifying the user on the user-preferred medium of correspondence.
Now, this can be done in two ways: either all these systems send commands to the Correspondence system OR the Correspondence system listens to all the business events and decides when to notify the user. Let’s try digging a bit deeper.
Visibility: This takes the visibility away from the Shipping domain of notification to the user as part of some operation.
Context Leakage: Correspondence will need to understand the logic of all other domains (Shipping in this case).
Resilience: If correspondence is temporarily down for some reason, an event in the queue will be retried a few times. This allows the shipping process to complete its processes with the assurance of guaranteed delivery of the event messages via service bus.
Martin Fowler also warns about this in his post on event driven systems:
“The danger is that it’s very easy to make nicely decoupled systems with event notification, without realizing that you’re losing sight of that larger-scale flow, and thus set yourself up for trouble in future years.”
As an alternative, we can convert the events to commands. This will move the logic and visibility back to the Shipping domain.
This solution addresses a couple of the concerns, but it brings back some of the pains which event-driven systems are an answer to.
No context leakage: Every system will own their logic/rules and content of the email/SMS for the correspondence (depends on how generic the command is). The correspondence does not need to know about all the other domains while acting as a generic infrastructure layer.
Visibility: In the event-driven system, there is a risk of losing the sight, as an event is a 1-to-many style communication so it can have multiple subscribers. Whereas a command is a many-to-1 style communication. Just by looking at the command you know who it is for and what service is going to handle it.
Transaction: The system sending a command can react to the outcome of the command. If the command fails, e.g. transaction commit vs rollback etc.
Tight coupling: Just like the Shipping system, any other system that needs to send the communication out will need to know the command. So any change in the command will impact all the systems using this command.
Single point of failure / Not resilient: When correspondence is down, other systems will fail to send any correspondence. So all the calling systems will need to build resiliency around it.
Best of Both Worlds
We need resilience and loosely coupled system without losing visibility of internal context of other domains. So what if we do the commands in the way we do events.
Command as an async Message
Sending commands as a message via service bus brings back the resiliency and still leaving the internal context of the shipping domain where it belongs. For example the Correspondence does not need to know if the user has to be notified of shipping, instead, it will just focus on the logic within its own context i.e. User preferences on correspondence and notification.
The calling system loses the Correspondence system from the transaction boundary. But it is OK in this case as the guaranteed message delivery delegates the onus on to the Correspondence to ensure the eventual delivery of the email/SMS.
In short, while working on the event-driven systems, we should try to identify all the business events and design architecture accordingly. If you struggle choosing your event name and cannot relate it to any real business event, it is better to be pragmatic about how our bounded contexts should communicate instead of attempting to shoehorn everything into events.