Saga

class Saga<A>(action: suspend SagaEffect.() -> A, compensation: suspend (A) -> Unit)

The saga design pattern is a way to manage data consistency across microservices in distributed transaction scenarios. A Saga is useful when you need to manage data in a consistent manner across services in distributed transaction scenarios. Or when you need to compose multiple actions with a compensation that needs to run in a transaction like style.

For example, let's say that we have the following domain types Order, Payment.

data class Order(val id: UUID, val amount: Long)
data class Payment(val id: UUID, val orderId: UUID)

The creation of an Order can only remain when a payment has been made. In SQL, you might run this inside a transaction, which can automatically roll back the creation of the Order when the creation of the Payment fails.

When you need to do this across distributed services, or a multiple atomic references, etc. You need to manually facilitate the rolling back of the performed actions, or compensating actions.

The Saga type, and saga DSL remove all the boilerplate of manually having to facilitate this with a convenient suspending DSL.

data class Order(val id: UUID, val amount: Long)
suspend fun createOrder(): Order = Order(UUID.randomUUID(), 100L)
suspend fun deleteOrder(order: Order): Unit = println("Deleting $order")

data class Payment(val id: UUID, val orderId: UUID)
suspend fun createPayment(order: Order): Payment = Payment(UUID.randomUUID(), order.id)
suspend fun deletePayment(payment: Payment): Unit = println("Deleting $payment")

suspend fun Payment.awaitSuccess(): Unit = throw RuntimeException("Payment Failed")

suspend fun main() {
saga {
val order = saga { createOrder() }.compensate(::deleteOrder).bind()
val payment = saga { createPayment(order) }.compensate(::deletePayment).bind()
payment.awaitSuccess()
}.transact()
}

Constructors

Saga
Link copied to clipboard
fun <A> Saga(action: suspend SagaEffect.() -> A, compensation: suspend (A) -> Unit)

Functions

compensate
Link copied to clipboard
infix fun compensate(compensate: suspend (A) -> Unit): Saga<A>

Add a compensating action to a Saga. A single Saga can have many compensating actions, they will be composed in a FILO order. This makes sure they're executed in reverse order as the actions.

transact
Link copied to clipboard
suspend fun transact(): A

Transact runs the Saga turning it into a suspend effect that results in A. If the saga fails then all compensating actions are guaranteed to run. When a compensating action failed it will be ignored, and the other compensating actions will continue to be run.

Properties

action
Link copied to clipboard
val action: suspend SagaEffect.() -> A
compensation
Link copied to clipboard
val compensation: suspend (A) -> Unit