This is part 2 of a 3 part series on untangling tightly coupled systems. Sign up for our newsletter using the footer form to be notified when part 3 is out. Plus, don't forget to register for Stephen's next webinar: Why EventStoreDB Decouples your Distributed Ball of Mud: A Beginner's Guide
In the last part, we talked about how asynchronous messages can be used to decouple systems that are highly dependent on each other.
In this part, we will examine problems you may experience when you implement this, and how EventStoreDB can help make this experience much simpler.
To make use of asynchronous messaging as described in the previous part, you have to achieve two things whenever your system makes a change. Firstly, you have to save it to the database and secondly, publish a message to all the external systems.
However, these two things must be done together, without fail, to keep the systems synchronized and consistent.
However, this can be quite hard to achieve with traditional (e.g. relational) databases and application patterns.
For example, when you receive a request for change, you would typically:
{ type = "orderSubmitted", orderId = 1, ... }
)However, you should be careful when you do so; if your system crashes while it’s messaging the other systems, then those messages can be lost forever because they were never saved in the first place. And this can have dire consequences to the consistency of your systems.
For example, imagine a customer updated their order’s delivery address in the database, but the system crashed before it was able to send that message over to the shipping system.
The message could be lost forever and then the package could be sent to the wrong address or go missing!
The data consistency problems that emerge from a change that writes to two separate systems is also known as The Dual Writes Problem.
The Dual Writes Problem refers to a challenge in distributed systems where data is written to two different storage systems, and inconsistencies may arise if one write succeeds and the other fails, or if they occur at slightly different times. This lack of coordination between the two writes can lead to data inconsistencies, potentially causing errors in application behavior.
The dual writes problem above can occur because a state change has to:
If this is not done together in some form of transaction, this can cause serious synchronization problems.
So instead of writing the state change in multiple places, why not just do it in one? And when there is no need for multiple writes, there will be no dual writes problem. Problem solved.
One way to approach this is to treat the message itself as the source of truth.
With EventStoreDB, you can store events to update the source of truth and use them as messages as well.
EventStoreDB is an event log that stores a complete sequence of state changes or business events of your business application. We call this the state-transition data model
You can also explore how EventStoreDB works here.
Events in EventStoreDB are simple data structures that represent state changes or business actions:
// OrderSubmittedEvent
{
"orderId": "202310-327",
"productID": "WIRELESS-HEADPHONE",
"quantity": 1,
"customerId": "TUN054",
"deliveryAddress": "123 Maple St., Springfield, IL"
...
}
// DeliveryAddressUpdated
{
"orderId": "202310-327",
"newDeliveryAddress": "122 Maple St., Springfield, IL",
...
}
// OrderDelivered
{
"orderId": "202310-327",
"deliveryTime": "20230101T125900",
...
}
And this also happens to be the perfect format for messages because external systems want to be notified with state changes.
Since events in EventStoreDB are both the source of truth AND the message itself, you only need to write once in order to update all your external systems. This overcomes the dual writes problem.
For example, when your system performs a state change, it will:
Try to send the event to each external system
The difference here, compared to the traditional way, is that even if your system crashes while it’s messaging the systems, you can always resend the event again because it has already been saved to the event log.
EventStoreDB also has built-in publish/subscribe APIs that make this easy to implement. For more information click here
In EventStoreDB, every event has a sequence number and an external system can use it to verify whether it is synchronized or not.
This is done by comparing the number of the latest events it received against EventStoreDB’s latest number. If it doesn’t match, then the external system can ask EventStoreDB to send the missing events so that it can synchronize its database with the latest update.
This makes implementing the message pattern a lot more easier and can help ensure no events are lost in the process.
When asynchronous messaging can be reliably implemented, your system becomes less dependent on external systems. Not only does this make your system cleaner and more compact, but it also allows you to scale it for higher throughput or availability.
With traditional databases and architectural patterns, you would often have to rely on multiple products and techniques to achieve this (e.g. Message broker, Distributed transactions, Outbox, Change Data Capture, etc.).
EventStoreDB, however, is able to achieve this with a single product, making it easier to implement with fewer moving parts.
In this part, we learnt that:
In the next part, we will take a step back and look at why EventStoreDB is so effective in decoupling your existing systems and other ways it can achieve it.