Transactional Outbox Pattern?-?Distributed Design?Patterns
Pratik Pandey
Senior Software Engineer at Booking.com | AWS Serverless Community Builder | pratikpandey.substack.com
As we deal with more complex distributed systems, we’ll often come across use cases where we need to atomically perform operations on multiple data sources in a consistent manner.?
So, let’s assume that we are persisting order data into an RDBMS. The ML team might want to perform some analytics on this data. So, we have the following options -
We’ve happily decoupled the Order Service with the Analytics Service and everyone is happy! (Or so you think!)
There are multiple failure scenarios here:
Outbox Pattern
Outbox Pattern comes to the rescue here. We make use of an Outbox table, which can be used to store the operations we’re performing on the database. Order Service will write to both the Order table as well as the Outbox table, as part of the same transaction, ensuring the operation will always be atomic(1).
Once the record is inserted into the Outbox table, it can be read by an asynchronous process(2) that reads the data and publishes it to the Message Broker(3).
QQ: What does the Outbox pattern remind you of? Hint: WAL
Advantages of Outbox?Pattern
The Outbox Pattern provides several benefits over other messaging patterns. Some of the major advantages of the Outbox Pattern are as follows:
领英推荐
Alternatives to Outbox?Pattern
If the Outbox Pattern is not suitable for your use case, there are a few alternative messaging patterns you can consider:
Sample Implementation
Here is a simple example of how you can implement the Outbox Pattern in Golang using a PostgreSQL database:
type Message struct
ID string `json:"id"`
EventType string `json:"event_type"`
Payload []byte `json:"payload"`
}{
2. Create an Outbox table in the database:
CREATE TABLE outbox
id uuid PRIMARY KEY,
event_type text NOT NULL,
payload bytes NOT NULL,
created_at timestamp NOT NULL DEFAULT NOW()
);(
3. Insert a message into the outbox table in a database transaction:
func sendMessage(db *sql.DB, message *Message) error
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
_, err := tx.Exec("INSERT INTO orders(id, order_value, order_qty) VALUES ($1, $2, $3)", ...)
if err != nil {
tx.Rollback()
return err
}
_, err := tx.Exec("INSERT INTO outbox(id, event_type, payload) VALUES ($1, $2, $3)", ...)
if err != nil {
tx.Rollback()
return err
}
err = tx.Commit()
if err != nil {
panic(err)
}
}
This brings us to the end of this article. We talked about the problem where the outbox pattern is really useful, the advantages of it and what the alternatives to the outbox pattern could be. We even see a sample snippet on how you could implement a transactional outbox pattern in Golang & Postgres. Please post comments on any doubts you might have and will be happy to discuss them!
Thank you for reading! I’ll be posting weekly content on distributed systems & patterns, so please like, share and subscribe to this newsletter for notifications of new posts.
Please comment on the post with your feedback, it will help me improve!?:)
Until next time, Keep asking questions & Keep learning!
Senior Software Engineer at Booking.com | AWS Serverless Community Builder | pratikpandey.substack.com
1 年Subscribe to my LinkedIn newsletter to get updates on any new System design posts -?https://www.dhirubhai.net/newsletters/system-design-patterns-6937319059256397824/ You can also follow me on Medium -?https://distributedsystemsmadeeasy.medium.com/subscribe
The GeekNarrator Podcast | Staff Engineer | Follow me for #distributedsystems #databases #interviewing #softwareengineering
1 年I like CDC (Change Data Capture) over outbox pattern because it loosely couples the application from data publishing. It is quite flexible and easy to configure. Also doesn’t need an additional table. Transaction logs already have the change data we need. Any specific use case where you think outbox pattern works better?