Record Scheduling
Outbox Service API
Schedule records for processing via the Outbox service:
@Service
class OrderService(
private val outbox: Outbox,
private val orderRepository: OrderRepository
) {
@Transactional
fun createOrder(command: CreateOrderCommand) {
val order = Order.create(command)
orderRepository.save(order)
// Schedule record with explicit key
outbox.schedule(
payload = OrderCreatedRecord(order.id, order.customerId),
key = "order-${order.id}" // Groups records for ordered processing
)
}
@Transactional
fun updateOrder(orderId: String) {
// Schedule record with auto-generated UUID key
outbox.schedule(payload = OrderUpdatedRecord(orderId))
}
}
@Service
public class OrderService {
private final Outbox outbox;
private final OrderRepository orderRepository;
public OrderService(Outbox outbox, OrderRepository orderRepository) {
this.outbox = outbox;
this.orderRepository = orderRepository;
}
@Transactional
public void createOrder(CreateOrderCommand command) {
Order order = Order.create(command);
orderRepository.save(order);
// Schedule record with explicit key
outbox.schedule(
new OrderCreatedRecord(order.getId(), order.getCustomerId()),
"order-" + order.getId() // Groups records for ordered processing
);
}
@Transactional
public void updateOrder(String orderId) {
// Schedule record with auto-generated UUID key
outbox.schedule(new OrderUpdatedRecord(orderId));
}
}
Record Lifecycle
Records go through the following states:
- NEW: Freshly scheduled, waiting for processing
- COMPLETED: Successfully processed by all handlers
- FAILED: Exhausted all retries, requires manual intervention
OutboxEventMulticaster
The OutboxEventMulticaster provides seamless integration with Spring's event system. It automatically intercepts and persists events annotated with @OutboxEvent directly to the outbox table, allowing you to use Spring's native ApplicationEventPublisher while getting outbox benefits.
How It Works
graph TB
A["publishEvent(event)"] --> B["OutboxEventMulticaster"]
B --> C{Check OutboxEvent<br/>Annotation}
C -->|Present| D["Serialize & Store"]
C -->|Not Present| E["Normal Event Flow"]
D --> F["Outbox Table"]
E --> G["Event Listeners"]
D -->|if publish-after-save=true| G
@OutboxEvent Annotation
Mark your events with @OutboxEvent to enable automatic outbox persistence:
@OutboxEvent(key = "#event.orderId") // SpEL expression for key resolution
data class OrderCreatedEvent(
val orderId: String,
val customerId: String,
val amount: BigDecimal
)
@OutboxEvent(key = "#event.orderId") // SpEL expression for key resolution
public class OrderCreatedEvent {
private String orderId;
private String customerId;
private BigDecimal amount;
// constructor, getters, setters...
}
SpEL Key Resolution
The key parameter supports Spring Expression Language (SpEL) for dynamic key extraction:
| Expression | Description | Example |
|---|---|---|
#root.fieldName |
Access root object property | #root.orderId |
#event.fieldName |
Same as #root (alternative syntax) | #event.customerId |
#root.getId() |
Call method on root object | #root.getOrderId() |
#root.nested.field |
Access nested properties | #root.order.id |
#root.toString() |
Convert to string | #root.id.toString() |
SpEL Examples
@OutboxEvent(key = "#event.orderId")
data class OrderEvent(val orderId: String)
@OutboxEvent(key = "#event.getAggregateId()")
data class DomainEvent(val id: UUID) {
fun getAggregateId() = id.toString()
}
@OutboxEvent(key = "#event.order.id")
data class OrderConfirmedEvent(val order: Order)
Configuration
The multicaster can be configured to control event publishing behavior:
namastack:
outbox:
multicaster:
enabled: true # Enable/disable automatic interception (default: true)
processing:
publish-after-save: true # Also forward to other listeners in same transaction (default: true)
| Configuration | Value | Effect |
|---|---|---|
multicaster.enabled |
true (default) |
@OutboxEvent annotations are intercepted and stored |
multicaster.enabled |
false |
OutboxEventMulticaster is disabled, events flow normally |
publish-after-save |
true (default) |
Events also published to other listeners in same transaction |
publish-after-save |
false |
Events only stored to outbox, not published to listeners |
Usage Example
@Service
class OrderService(
private val orderRepository: OrderRepository,
private val eventPublisher: ApplicationEventPublisher
) {
@Transactional
fun createOrder(command: CreateOrderCommand) {
val order = Order.create(command)
orderRepository.save(order)
// Automatically saved to outbox + published to listeners
eventPublisher.publishEvent(
OrderCreatedEvent(order.id, order.customerId, order.amount)
)
}
}
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
public OrderService(OrderRepository orderRepository,
ApplicationEventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public void createOrder(CreateOrderCommand command) {
Order order = Order.create(command);
orderRepository.save(order);
// Automatically saved to outbox + published to listeners
eventPublisher.publishEvent(
new OrderCreatedEvent(order.getId(), order.getCustomerId(), order.getAmount())
);
}
}
@OutboxEvent with Context
When using @OutboxEvent with ApplicationEventPublisher, you can define context using SpEL expressions:
@OutboxEvent(
key = "#this.orderId",
context = [
OutboxContextEntry(key = "customerId", value = "#this.customerId"),
OutboxContextEntry(key = "region", value = "#this.region"),
OutboxContextEntry(key = "priority", value = "#this.priority")
]
)
data class OrderCreatedEvent(
val orderId: String,
val customerId: String,
val region: String,
val priority: String
)
@OutboxEvent(
key = "#this.orderId",
context = {
@OutboxContextEntry(key = "customerId", value = "#this.customerId"),
@OutboxContextEntry(key = "region", value = "#this.region"),
@OutboxContextEntry(key = "priority", value = "#this.priority")
}
)
public class OrderCreatedEvent {
private String orderId;
private String customerId;
private String region;
private String priority;
// constructor, getters...
}
Context from @OutboxEvent annotations is merged with context from registered OutboxContextProvider beans.