Articles

Untangle Tightly Coupled Systems with EventStoreDB (Part 2)

Stephen Tung  |  20 September 2023

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.

Problems Implementing Asynchronous Messaging with Traditional Database

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.

The Dual Writes Problem

For example, when you receive a request for change, you would typically:

  1. Save the change to your tables
  2. Craft a message that represents the change (e.g. { type = "orderSubmitted", orderId = 1, ... })
  3. Try to send the message to each external system (typically via some message queue)

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!

ES-Blog-Untangle-5-1

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.

 

Implementing Asynchronous Messaging with EventStoreDB

Why Dual Writes when you can Write Once and Update Everywhere?

The dual writes problem above can occur because a state change has to:

  1. Update the source of truth, and
  2. Store the message

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.

Use Events as Source of Truth and Messaging with EventStoreDB

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.

Solve Dual Writes Problem with EventStoreDB

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:

  1. Save the event (i.e. message) to EventStoreDB (i.e. source of truth)
  2. Try to send the event to each external system

ES-Blog-Untangle-6-1

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

Ensuring External Systems are Synchronized with EventStoreDB

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.

ES-Blog-Untangle-7-1

Decoupling External Systems with EventStoreDB

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.

Conclusion

In this part, we learnt that:

  • Asynchronous messaging is hard to implement with traditional databases and application patterns because of the Dual Writes Problem.
  • EventStoreDB can effectively solve this problem because
    • It is both the source of truth and a message store
    • It has the capability to verify whether it is synchronized with external systems

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.


Photo of Stephen Tung

Stephen Tung Stephen has been a software practitioner and leader focused on simplifying and tackling the heart of complex business problems. He discovered DDD/CQRS/ES 15 years ago and has never looked back since. He's the father of three, living in Hong Kong, and enjoys to zen out when there is a moment.