Outbox Feature
Outbox feature
The outbox feature implements the transactional outbox pattern — persisting domain events to a database table within the same transaction as the aggregate save, then draining them asynchronously to the event bus. This ensures reliable event delivery.
Requires gorm or bun (an ORM must be enabled).
What it provides
| File | Purpose |
|---|---|
internal/domain/outbox/event.go |
Outbox event domain type |
internal/domain/outbox/repository.go |
Outbox repository port |
internal/adapters/persistence/bun/outbox_repository.go |
Bun-backed outbox repository (when bun enabled) |
internal/adapters/persistence/gorm/outbox_repository.go |
GORM-backed outbox repository (when gorm enabled) |
internal/adapters/outbox/bun_uow.go |
Bun transactional Unit of Work (when bun enabled) |
internal/adapters/outbox/gorm_uow.go |
GORM transactional Unit of Work (when gorm enabled) |
internal/adapters/outbox/worker.go |
Background poller that drains the outbox to the event bus |
migrations/000002_add_outbox_events.up.sql |
Outbox table schema |
migrations/000002_add_outbox_events.down.sql |
Outbox table rollback |
How it works
- Application handler creates aggregate + domain events
- Unit of Work saves the aggregate and writes events to
outbox_eventsin a single database transaction - Background worker polls the outbox table periodically
- Worker publishes unpublished events to the in-memory event bus
- Published events are deleted (at-least-once delivery)
This eliminates the window between “aggregate saved” and “event published” that the default in-memory UoW has.
Tech stack
| Library | Purpose | Documentation |
|---|---|---|
| PostgreSQL | Transactional storage for the outbox table | postgresql.org/docs |
| golang-migrate | Migrations for the outbox table | github.com/golang-migrate/migrate |
(Depends on either Bun or GORM — see the respective feature docs.)
Learning resources
- Transactional outbox pattern — microservices.io/patterns/data/transactional-outbox
- Outbox pattern explained — engineeringblog.yelp.com/2021/06/building-reliable-events.html
Notes
- The outbox feature replaces the default in-memory UoW with a transaction-backed version.
- The worker runs in a goroutine inside the server process. For production, consider running it as a separate process to avoid affecting request latency.
- At-least-once delivery means subscribers should be idempotent.
- Polling interval is configurable in
configs/config.yamlunder theoutboxsection.