Introduction
This sample shows a simple saga to manage an order.
Once the project is started, press 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 StartOrder.
property on the message with OrderSagaData.
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 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
endpointConfiguration.UsePersistence<LearningPersistence>();
endpointConfiguration.UseTransport(new LearningTransport());
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
The Saga
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.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;
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);
var timeout = DateTimeOffset.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);
}
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.
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(DateTimeOffset dateTime)
{
return Path.Combine(TransportDirectory, ".delayed", dateTime.UtcDateTime.ToString("yyyyMMddHHmmss"));
}
public static string GetSagaLocation<T>(Guid sagaId)
where T : Saga
{
return Path.Combine(SagaDirectory, typeof(T).Name, $"{sagaId}.json");
}
}