r/SpringBoot • u/wonderful_unikorn • 12h ago
Discussion Transactional Outbox is harder than you think
I had to write a transactional outbox for two of my projects. The popular solutions didn't fit, and CDC with Debezium felt too heavy for "save an event together with the data, send it later".
We all know things often look simpler on paper than in reality, right? What's there to do, really? Just write to a table, poll it, publish the messages - easy, right?
Not so fast. Building a production outbox is like growing a stalactite: it slowly accretes features, scripts and workarounds. So I want to share my hard-won solution, which I wrapped into a simple, fairly compact, ready-to-use starter that has (I hope) everything you need.
Guarantees (with caveats):
- At-least-once delivery with retries and backoff.
- Effectively-once processing through an inbox (deduplication on the consumer side).
- At-most-once processing through the inbox, for side effects like emails where a duplicate is worse than a miss.
Two design decisions I’m happy with:
- Adaptive polling. While an event type has nothing to send, the pause between polls grows up to a maximum, and drops back to the minimum as soon as events appear. This noticeably reduces DB load.
- Per-event-type settings. Each type has its own poll interval, batch size and retry count. Critical events go out within a fraction of a second, while reports can wait a few minutes when there’s no activity.
Repo: https://github.com/dmitriy-iliyov/oncebox
Feedback is very welcome. If you’ve built an outbox in Spring yourself, what was your biggest pain point?
