Skip to main content

SagaBuilder

The fluent DSL for defining saga steps.


API

public interface SagaBuilder<C> {

/**
* Begins the definition of a new step.
*/
StepBuilder<C> step(String name);

/**
* Finalizes the saga definition.
*/
void build();
}

StepBuilder

Each step is configured through StepBuilder:

public interface StepBuilder<C> {

/**
* Defines the step execution function.
*/
StepBuilder<C> invoke(Function<C, C> action);

/**
* Defines the compensation function (rollback).
*/
StepBuilder<C> compensate(Consumer<C> compensation);

/**
* Configures the retry policy.
*/
StepBuilder<C> retry(RetryPolicy policy);

/**
* Adds a timeout to the step.
*/
StepBuilder<C> timeout(Duration timeout);

/**
* Defines a condition for step execution.
*/
StepBuilder<C> when(Predicate<C> condition);

/**
* Begins the next step (chaining).
*/
StepBuilder<C> step(String name);

/**
* Finalizes the definition.
*/
void build();
}

Examples

Basic Step

builder
.step("validate")
.invoke(this::validate)
.build();

Step with Compensation

builder
.step("block-balance")
.invoke(this::blockBalance)
.compensate(this::unblockBalance)
.build();

Step with Retry

builder
.step("call-external-api")
.invoke(this::callApi)
.compensate(this::rollbackApi)
.retry(exponential(3, Duration.ofSeconds(1)))
.build();

Step with Timeout

builder
.step("slow-operation")
.invoke(this::slowOperation)
.timeout(Duration.ofSeconds(30))
.build();

Conditional Step

builder
.step("send-premium-notification")
.invoke(this::sendPremiumNotification)
.when(ctx -> ctx.isPremiumUser())
.build();

Full Chain

@Override
public void define(SagaBuilder<OrderContext> builder) {
builder
// Step 1: COMPENSABLE
.step("reserve-stock")
.invoke(this::reserveStock)
.compensate(this::releaseStock)
.timeout(Duration.ofSeconds(5))

// Step 2: COMPENSABLE
.step("process-payment")
.invoke(this::processPayment)
.compensate(this::refundPayment)
.retry(exponential(3, Duration.ofSeconds(2)))

// Step 3: PIVOT (no compensation)
.step("confirm-order")
.invoke(this::confirmOrder)
.retry(exponential(5, Duration.ofSeconds(1)))

// Step 4: CONDITIONAL
.step("send-vip-gift")
.invoke(this::sendVipGift)
.when(ctx -> ctx.isVipCustomer())

// Step 5: RETRIABLE
.step("send-confirmation")
.invoke(this::sendConfirmation)
.retry(infinite())

.build();
}

Invoke Methods

Signature

Function<C, C> action

The method receives the current context and must return a new context (immutability).

Example

private OrderContext reserveStock(OrderContext ctx) {
// Executes the operation
String reservationId = stockService.reserve(ctx.items());

// Returns new context with the result
return ctx.withReservationId(reservationId);
}

Compensate Methods

Signature

Consumer<C> compensation

The method receives the context and returns nothing (void).

Example

private void releaseStock(OrderContext ctx) {
// Uses data saved in the context to revert
stockService.release(ctx.reservationId());
}


Kotlin

The Java builder has overloaded invoke() and compensate() methods that cause SAM conversion ambiguity in Kotlin. The sagaweaw-kotlin module provides a block-based DSL that resolves this:

// Java — requires explicit Consumer<T>
saga.step("charge-payment")
.invoke(Consumer { ctx -> paymentService.charge(ctx.amount) })
.compensate(Consumer { ctx -> paymentService.refund(ctx.orderId) })

// Kotlin DSL — clean lambdas, no type annotations
saga.step("charge-payment") {
invoke { ctx -> paymentService.charge(ctx.amount) }
compensate { ctx -> paymentService.refund(ctx.orderId) }
retry(exponentialRetry(3, 5.seconds))
}

See the Kotlin guide → for setup.