Features
Namastack Outbox for Spring Boot provides a comprehensive set of features to implement the Outbox Pattern in distributed systems with reliability, scalability, and ease of use.
Core Features
Transactional Outbox Pattern
Zero Message Loss
The library ensures that domain events are never lost by storing them in the same database transaction as your business data. This guarantees consistency between your domain state and published events.
Benefits
- ACID Compliance: Events are saved atomically with business data
- Consistency Guarantee: No partial updates or lost events
- Failure Recovery: System crashes don't result in data loss
- Exactly-once semantics: Events are processed reliably
How it Works
sequenceDiagram
participant A as Application
participant DB as Database
participant O as Outbox
participant P as Processor
A->>DB: Begin Transaction
A->>DB: Save Business Data
A->>O: Save Event
DB->>A: Commit Transaction
P->>O: Poll Events
P->>P: Process Event
P->>O: Mark Complete
Distributed Locking
Concurrent Processing Prevention
Each aggregate gets its own distributed lock to prevent multiple instances from processing the same events simultaneously while allowing different aggregates to be processed in parallel.
- Per-Aggregate Locking: Fine-grained control prevents bottlenecks
- Automatic Expiration: Locks expire to prevent deadlocks
- Lock Renewal: Active processors can extend their locks
- Optimistic Locking: Prevents race conditions during renewal
- Horizontal Scaling: Multiple instances can work on different aggregates
outbox:
locking:
extension-seconds: 300 # Lock duration (5 minutes)
refresh-threshold: 60 # Renew when < 60s remaining
Event Ordering
Guaranteed Processing Order
Events for the same aggregate are always processed in creation order, ensuring business logic consistency and preventing race conditions.
Key Benefits:
- Aggregate Consistency: Events within an aggregate maintain order
- Business Logic Safety: Dependent events process in correct sequence
- Parallel Aggregates: Different aggregates process independently
- Scalable Design: No global ordering bottlenecks
Control how the scheduler handles failures within aggregates:
Stop on First Failure
outbox:
processing:
stop-on-first-failure: true
- When one event fails, processing stops for remaining events in that aggregate
- Maintains strict event ordering within aggregates
- Prevents cascading issues from dependent events
- Recommended: When events within an aggregate have dependencies
outbox:
processing:
stop-on-first-failure: false
- Failed events don't block independent events in the same aggregate
- Maximizes throughput for independent events
- Recommended: When events within an aggregate are independent
Behavior Comparison:
Configuration | Event 1 | Event 2 | Event 3 | Result |
---|---|---|---|---|
true (default) |
Success | Fails | Skipped | Event 2 retried, Event 3 waits |
false |
Success | Fails | Success | Event 2 retried independently |
Advanced Configuration
Retry Mechanisms
The library provides sophisticated retry strategies to handle transient failures gracefully.
outbox:
retry:
policy: "fixed"
max-retries: 5
fixed:
delay: 5000 # 5 seconds between retries
Use Case: Simple scenarios with consistent retry intervals
outbox:
retry:
policy: "exponential"
max-retries: 10
exponential:
initial-delay: 1000 # Start with 1 second
max-delay: 300000 # Cap at 5 minutes
multiplier: 2.0 # Double each time
Retry Schedule: 1s → 2s → 4s → 8s → 16s → ... (up to max-delay)
outbox:
retry:
policy: "jittered"
max-retries: 7
jittered:
base-policy: exponential
jitter: 1000 # Add 0-1000ms random delay
exponential:
initial-delay: 2000
max-delay: 60000
multiplier: 2.0
Benefits: Prevents thundering herd problems in high-traffic systems
Monitoring & Observability
Including the Metrics Module
Add Dependency
To enable monitoring and observability features, include the namastack-outbox-metrics
module in your project:
dependencies {
implementation("io.namastack:namastack-outbox-starter-jpa")
implementation("io.namastack:namastack-outbox-metrics")
// For Prometheus endpoint (optional)
implementation("io.micrometer:micrometer-registry-prometheus")
}
<dependencies>
<dependency>
<groupId>io.namastack</groupId>
<artifactId>namastack-outbox-starter-jpa</artifactId>
<version>${namastack-outbox.version}</version>
</dependency>
<dependency>
<groupId>io.namastack</groupId>
<artifactId>namastack-outbox-metrics</artifactId>
<version>${namastack-outbox.version}</version>
</dependency>
<!-- For Prometheus endpoint (optional) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
Built-in Metrics
Micrometer Integration
The namastack-outbox-metrics
module provides automatic integration with Spring Boot Actuator and Micrometer.
Metric | Description | Tags |
---|---|---|
outbox.records.count |
Number of outbox records | status=new\|failed\|completed |
Endpoints:
-
/actuator/metrics/outbox.records.count
-
/actuator/prometheus
(if Prometheus enabled)
outbox_records_count{status="new"} 42
outbox_records_count{status="failed"} 3
outbox_records_count{status="completed"} 1337
# Get current metrics
curl http://localhost:8080/actuator/metrics/outbox.records.count
# Prometheus endpoint
curl http://localhost:8080/actuator/prometheus | grep outbox
Status Monitoring
Monitor outbox status programmatically:
@Service
class OutboxMonitoringService(
private val outboxRepository: OutboxRecordRepository
) {
fun getPendingEvents(): List<OutboxRecord> =
outboxRepository.findPendingRecords()
fun getFailedEvents(): List<OutboxRecord> =
outboxRepository.findFailedRecords()
fun getCompletedEvents(): List<OutboxRecord> =
outboxRepository.findCompletedRecords()
}
Performance Features
High Throughput
Optimized for Scale
- Batch Processing: Multiple events processed efficiently
- Connection Pooling: Database connections managed optimally
- Minimal Overhead: Lightweight processing with low latency
- Concurrent Aggregates: Parallel processing across different aggregates
Race Condition Safety
- Uses database-level optimistic locking
- Prevents concurrent modifications
- Automatic retry on version conflicts
- No performance penalty for read operations
- Different aggregates process independently
- No global locks or bottlenecks
- Scales horizontally across instances
- Maintains per-aggregate ordering guarantees
Developer Experience
Easy Integration
Minimal Setup Required
- Add Dependency: Single JAR includes everything needed
- Enable Annotation:
@EnableOutbox
activates all features - Configure Database: Automatic schema creation available
- Implement Processor: Simple interface for event handling
val outboxRecord = OutboxRecord.Builder()
.aggregateId(order.id.toString())
.eventType("OrderCreated")
.payload(objectMapper.writeValueAsString(event))
.build(clock)
val outboxRecord = OutboxRecord.restore(
id = UUID.randomUUID().toString(),
aggregateId = order.id.toString(),
eventType = "OrderCreated",
payload = objectMapper.writeValueAsString(event),
createdAt = OffsetDateTime.now(clock),
status = OutboxRecordStatus.NEW,
completedAt = null,
retryCount = 0,
nextRetryAt = OffsetDateTime.now(clock)
)
Testing Support
- Unit Tests: All components with high coverage
- Integration Tests: Real database and locking scenarios
- Concurrency Tests: Race condition validation
- Performance Tests: High-throughput scenarios
./gradlew test
Database Support
Broad Compatibility
Works with any JPA-supported database:
- PostgreSQL (Recommended)
- MySQL
- H2 (Development/Testing)
- SQL Server
- :material-oracle: Oracle
- And any other JPA-compatible database
# Automatic schema creation
outbox:
schema-initialization:
enabled: true
Or use manual SQL scripts for production deployments.
Reliability Guarantees
What Namastack Outbox for Spring Boot Guarantees
- At-least-once delivery: Events will be processed at least once
- Ordering per aggregate: Events for the same aggregate are processed in order
- Failure recovery: System failures don't result in lost events
- Scalability: Multiple instances can process different aggregates concurrently
- Consistency: Database transactions ensure data integrity
- Eventual consistency: Failed events are automatically retried
What Namastack Outbox for Spring Boot Does NOT Guarantee
- Exactly-once delivery: Events may be processed multiple times (your handlers should be idempotent)
- Global ordering: No ordering guarantee across different aggregates
- Real-time processing: Events are processed asynchronously with configurable delays
Next Steps
Ready to get started? Check out the Quick Start Guide to integrate Namastack Outbox for Spring Boot into your application.