Simple Saga Usage

Component: NServiceBus | Nuget: NServiceBus (Version: 6.x)

Introduction

This sample shows a simple saga.

Once starting the sample, press Enter to send a StartOrder message with a random OrderId. Each message will cause a saga instance to start because StartOrder is configured to start a saga using the IAmStartedByMessages construct. There is also a mapping defined between StartOrder.OrderId and OrderSagaData.OrderId. This mapping helps to correlate incoming messages to its appropriate saga instances.

This sample also requests a 30 second CancelOrder timeout that will mark the saga as complete if the saga is not already complete.

The output to the console will be

Storage locations:
Learning Persister: SolutionDir\Sample\bin\Debug\.sagas
Learning Transport: SolutionDir\

Press 'Enter' to send a StartOrder message
Press any other key to exit

Sent StartOrder with OrderId 8d80b684-cc77-4ec6-867b-090bc38d914c.

OrderSaga StartOrder received with OrderId 8d80b684-cc77-4ec6-867b-090bc38d914c
OrderSaga Sending a CompleteOrder that will be delayed by 10 seconds
Stop the endpoint now to see the saga data in:
SolutionDir\Sample\bin\Debug\.sagas\OrderSaga\f5e7ea90-b866-16a0-c911-452f954b95da.json
OrderSaga Requesting a CancelOrder that will be executed in 30 seconds.
Stop the endpoint now to see the timeout data in the delayed directory
SolutionDir\.delayed\20170510054055

Endpoint configuration

Edit
endpointConfiguration.UsePersistence<LearningPersistence>();
endpointConfiguration.UseTransport<LearningTransport>();

The Saga

Edit
public class OrderSaga :
    Saga<OrderSagaData>,
    IAmStartedByMessages<StartOrder>,
    IHandleMessages<CompleteOrder>,
    IHandleTimeouts<CancelOrder>
{
    static ILog log = LogManager.GetLogger<OrderSaga>();

    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderSagaData> mapper)
    {
        mapper.ConfigureMapping<StartOrder>(message => message.OrderId)
                .ToSaga(sagaData => sagaData.OrderId);
        mapper.ConfigureMapping<CompleteOrder>(message => message.OrderId)
                .ToSaga(sagaData => sagaData.OrderId);
    }

    public async Task Handle(StartOrder message, IMessageHandlerContext context)
    {
        Data.OrderId = message.OrderId;
        log.Info($"StartOrder received with OrderId {message.OrderId}");

        log.Info($@"Sending a CompleteOrder that will be delayed by 10 seconds
Stop the endpoint now to see the saga data in:
{LearningLocationHelper.GetSagaLocation<OrderSaga>(Data.Id)}");
        var completeOrder = new CompleteOrder
        {
            OrderId = Data.OrderId
        };
        var sendOptions = new SendOptions();
        sendOptions.DelayDeliveryWith(TimeSpan.FromSeconds(10));
        sendOptions.RouteToThisEndpoint();
        await context.Send(completeOrder, sendOptions)
            .ConfigureAwait(false);

        var timeout = DateTime.UtcNow.AddSeconds(30);
        log.Info($@"Requesting a CancelOrder that will be executed in 30 seconds.
Stop the endpoint now to see the timeout data in the delayed directory
{LearningLocationHelper.TransportDelayedDirectory(timeout)}");
        await RequestTimeout<CancelOrder>(context, timeout)
            .ConfigureAwait(false);
    }

    public Task Handle(CompleteOrder message, IMessageHandlerContext context)
    {
        log.Info($"CompleteOrder received with OrderId {message.OrderId}");
        MarkAsComplete();
        return Task.CompletedTask;
    }

    public Task Timeout(CancelOrder state, IMessageHandlerContext context)
    {
        log.Info($"CompleteOrder not received soon enough OrderId {Data.OrderId}. Calling MarkAsComplete");
        MarkAsComplete();
        return Task.CompletedTask;
    }
}

Location Helper

This is a helper class used by the sample to derive the storage locations of the Learning Transport and the Learning Persistence.

Edit
public static class LearningLocationHelper
{
    public static string TransportDirectory;
    public static string SagaDirectory;

    static LearningLocationHelper()
    {
        var location = Assembly.GetExecutingAssembly().Location;
        var runningDirectory = Directory.GetParent(location).FullName;
        SagaDirectory = Path.Combine(runningDirectory, ".sagas");
        TransportDirectory = Path.GetFullPath(Path.Combine(runningDirectory, @"..\..\..\"));
    }

    public static string TransportDelayedDirectory(DateTime dateTime)
    {
        return Path.Combine(TransportDirectory, ".delayed", dateTime.ToString("yyyyMMddHHmmss"));
    }

    public static string GetSagaLocation<T>(Guid sagaId)
        where T : Saga
    {
        return Path.Combine(SagaDirectory, typeof(T).Name, $"{sagaId}.json");
    }
}

Related Articles


Last modified