PouryaKit
Concepts

Saga

A Module's long-running reaction across Integration Events that keeps its own state between steps, and the one reaction that runs outside an Operation.

What it is

A Saga is a Module's long-running reaction across Integration Events that keeps its own state between steps. Integration Events start and advance it, its own timeouts end it, and each step can emit Integration Events of its own. A saga's identity is the tenant of the event that started it with that event's business id.

A Saga is the one reaction that runs outside an Operation. Each step runs with no Operation Context, is claimed once in its own transaction, and emits its Integration Events under the saga's system Actor.

A step only changes the saga's state, emits and schedules

No authorizer, validator, Unit of Work or Receipt runs for a step. A step that dies before its commit runs again, so a call to another service or a file it wrote would repeat. Anything else is the work of a subscription that receives what the saga emits.

When to use it

When a reaction waits for several Integration Events, keeps what it has seen between them, and gives up after a time. In the Kit's tests, a fulfilment saga starts when an order is placed, counts its payments, and either fulfils the order once all payments arrived or cancels it when they did not arrive in time. A reaction to one Integration Event at a time is an Integration Event Handler: see Integration Events.

How it works

A Module declares its saga in its Composition Root: its name and system Actor, the Integration Events that start or advance it with the business id each one names, and its timeouts.

ShippingModule.cs
module.AddPouryaKitSaga<FulfilmentSaga>(
    Fulfilment,
    systemActor: Fulfilment,
    saga =>
        saga.Input<OrderPlacedV1>(placed => placed.OrderId)
            .Input<PaymentReceivedV1>(paid => paid.OrderId)
            .Input<PaymentReceivedV2>(paid => paid.OrderId)
            .Timeout<FulfilmentTimeout>()
);

The saga derives from PouryaKitSaga. A starting step, named Start, returns the new saga and the timeouts it schedules:

ShippingModule.cs
/// <summary>Starts the saga, and schedules its timeout.</summary>
public static (FulfilmentSaga, FulfilmentTimeout) Start(
    SagaInput<FulfilmentSaga, OrderPlacedV1> placed
) =>
    (
        new FulfilmentSaga
        {
            Order = placed.Event.OrderId,
            Installments = placed.Event.Installments,
        },
        new FulfilmentTimeout(TimeSpan.FromSeconds(placed.Event.PayWithinSeconds))
    );

An advancing step, named Handle, takes a later event or a timeout. It emits with PublishAsync and completes the saga with MarkCompleted:

ShippingModule.cs
/// <summary>The payments did not arrive in time.</summary>
public async Task Handle(FulfilmentTimeout timeout)
{
    await PublishAsync(new OrderCancelledV1(Order, Paid));
    MarkCompleted();
}

The Host runs sagas on Wolverine's saga storage in its PostgreSQL message store, with UsePouryaKitWolverine. Outside Development the Host loads each saga's generated code, which the step before a release writes: see Prepare a release.

What the Kit guarantees

GuaranteeStated in
A route of its own, one claim per step, the tenant in the identity, emissions an ordinary subscription receivesWhat the Kit guarantees
What a step must not doWhat a step must not do
What fails the Host when it is built or startedThe Host
What a saga cannot do yetLimits

Failure messages

Next steps

On this page