Skip to main content
Version: Next

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))
}
}

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

@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
)

SpEL Key Resolution

The key parameter supports Spring Expression Language (SpEL) for dynamic key extraction:

ExpressionDescriptionExample
#root.fieldNameAccess root object property#root.orderId
#event.fieldNameSame as #root (alternative syntax)#event.customerId
#root.getId()Call method on root object#root.getOrderId()
#root.nested.fieldAccess nested properties#root.order.id
#root.toString()Convert to string#root.id.toString()
@OutboxEvent(key = "#event.orderId")
data class OrderEvent(val orderId: String)

Configuration

The multicaster can be configured to control event publishing behavior:

namastack:
outbox:
multicaster:
enabled: true # Enable/disable automatic interception (default: true)
publish-after-save: true # Also forward to other listeners in same transaction (default: true)
ConfigurationValueEffect
multicaster.enabledtrue (default)@OutboxEvent annotations are intercepted and stored
multicaster.enabledfalseOutboxEventMulticaster is disabled, events flow normally
publish-after-savetrue (default)Events also published to other listeners in same transaction
publish-after-savefalseEvents 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)
)
}
}

@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
)

Context from @OutboxEvent annotations is merged with context from registered OutboxContextProvider beans.