Skip to content

Integration with Sagas

Http endpoints can start Wolverine sagas by just using a return value for a Saga value.

Let's say that we have a stateful saga type for making online reservations like this:

cs
public class Reservation : Saga
{
    public string? Id { get; set; }
    
    // Apply the CompleteReservation to the saga
    public void Handle(BookReservation book, ILogger<Reservation> logger)
    {
        logger.LogInformation("Completing Reservation {Id}", book.Id);

        // That's it, we're done. Delete the saga state after the message is done.
        MarkCompleted();
    }

    // Delete this Reservation if it has not already been deleted to enforce a "timeout"
    // condition
    public void Handle(ReservationTimeout timeout, ILogger<Reservation> logger)
    {
        logger.LogInformation("Applying timeout to Reservation {Id}", timeout.Id);

        // That's it, we're done. Delete the saga state after the message is done.
        MarkCompleted();
    }
}

snippet source | anchor

To start the Reservation saga, you could use an HTTP endpoint method like this one:

cs
[WolverinePost("/reservation")]
public static (
    // The first return value would be written out as the HTTP response body
    ReservationBooked, 
    
    // Because this subclasses from Saga, Wolverine will persist this entity
    // with saga persistence
    Reservation, 
    
    // Other return values that trigger no special handling will be treated
    // as cascading messages
    ReservationTimeout) Post(StartReservation start)
{
    return (new ReservationBooked(start.ReservationId, DateTimeOffset.UtcNow), new Reservation { Id = start.ReservationId }, new ReservationTimeout(start.ReservationId));
}

snippet source | anchor

Remember in Wolverine.HTTP that the first return value of an endpoint is assumed to be the response body by Wolverine, so if you are wanting to start a new saga from an HTTP endpoint, the Saga return value either has to be a secondary value in a tuple to opt into the Saga mechanics. Alternatively, if all you want to do is create a new saga, but nothing else, you can return the Saga type and force Wolverine to use the return value as a new Saga as shown in the snippet below. Please note that when creating a Saga entity in this manner, if it has a static Start() method, it will not be invoked. Other message handlers in the Saga will behave as usual.

cs
[WolverinePost("/reservation2")]

// This directs Wolverine to disregard the Reservation return value
// as the response body, and allow Wolverine to use the Reservation
// return as a new saga
[EmptyResponse] 
public static Reservation Post2(StartReservation start)
{
    return new Reservation { Id = start.ReservationId };
}

snippet source | anchor

Released under the MIT License.