﻿# Simple Saga Usage



## Introduction

This sample shows a simple saga to manage an order.

Once the project is started, press <kbr>Enter</kbr> to send a `StartOrder` message with a randomly assigned `OrderId`. This message starts a new instance of the `OrderSaga` saga as specified  by the `IAmStartedByMessages` interface and mapping that [correlates](/nservicebus/sagas/message-correlation.md) `StartOrder.OrderId` property on the message with `OrderSagaData.OrderId` property on the saga data.

When processing the `StartOrder` message each saga instance requests a 30-second `CancelOrder` timeout that will [mark the saga as complete](/nservicebus/sagas/index.md#ending-a-saga) 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

<!-- snippet: config -->

```cs
endpointConfiguration.UsePersistence<LearningPersistence>();
endpointConfiguration.UseTransport(new LearningTransport());
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
```

<!-- endsnippet -->


## The Saga

<!-- snippet: thesaga -->

```cs
public class OrderSaga(ILogger<OrderSaga> logger) :
    Saga<OrderSagaData>,
    IAmStartedByMessages<StartOrder>,
    IHandleMessages<CompleteOrder>,
    IHandleTimeouts<CancelOrder>
{
    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderSagaData> mapper)
    {
        mapper.MapSaga(sagaData => sagaData.OrderId)
            .ToMessage<StartOrder>(message => message.OrderId)
            .ToMessage<CompleteOrder>(message => message.OrderId);
    }

    public async Task Handle(StartOrder message, IMessageHandlerContext context)
    {
        // Correlation property Data.OrderId is automatically assigned with the value from message.OrderId;
        logger.LogInformation("StartOrder received with OrderId {MessageOrderId}", message.OrderId);

        logger.LogInformation(@"Sending a CompleteOrder that will be delayed by 10 seconds
                                Stop the endpoint now to see the saga data in:
                                {GetSagaLocation}",
                                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);

        var timeout = DateTimeOffset.UtcNow.AddSeconds(30);

        logger.LogInformation(@"Requesting a CancelOrder that will be executed in 30 seconds.
                                Stop the endpoint now to see the timeout data in the delayed directory
                                {TransportDelayedDirectory}",
                                LearningLocationHelper.TransportDelayedDirectory(timeout));

        await RequestTimeout<CancelOrder>(context, timeout);
    }

    public Task Handle(CompleteOrder message, IMessageHandlerContext context)
    {
        logger.LogInformation("CompleteOrder received with OrderId {MessageOrderId}", message.OrderId);
        MarkAsComplete();
        return Task.CompletedTask;
    }

    public Task Timeout(CancelOrder state, IMessageHandlerContext context)
    {
        logger.LogInformation(@"CompleteOrder not received soon enough OrderId {DataOrderId}.
                                Calling MarkAsComplete",
                                Data.OrderId);
        MarkAsComplete();
        return Task.CompletedTask;
    }
}
```

<!-- endsnippet -->


## Location Helper

This is a helper class used by the sample to derive the storage locations of the [Learning Transport](/transports/learning/index.md) and the [Learning Persistence](/persistence/learning/index.md).

<!-- snippet: LearningLocationHelper -->

```cs
public static class LearningLocationHelper
{
    public static readonly string TransportDirectory;
    public static readonly 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(DateTimeOffset dateTime) =>
        Path.Combine(TransportDirectory, ".delayed", dateTime.UtcDateTime.ToString("yyyyMMddHHmmss"));

    public static string GetSagaLocation<T>(Guid sagaId)
        where T : Saga =>
        Path.Combine(SagaDirectory, typeof(T).Name, $"{sagaId}.json");
}
```

<!-- endsnippet -->
