NSaga – Lightweight Saga Management Framework For .Net

Ladies and gentlement, I’m glad to present you NSaga – lightweight saga management framework for .Net. This is something I’ve been working for the last few months and now can happily annonce the first public release. NSaga gives ability to create and manage sagas without having to write any plumbing code yourself.

Saga is a multi-step operation or activity that has persisted state and is operated by messages. Saga defines behaviour and state, but keeps them distinctly separated.

Saga classes are defined by ISaga<TSagaData> interface and take messages. Messages are directed by a SagaMediator. Comes with an internal DI container, but you can use your own. Comes with SQL server persistence, but others will follow shortly.

Basic saga will look like this:

public class ShoppingBasketSaga : ISaga<ShoppingBasketData>,
    InitiatedBy<StartShopping>,
    ConsumerOf<AddProductIntoBasket>,
    ConsumerOf<NotifyCustomerAboutBasket>
{
    public Guid CorrelationId { get; set; }
    public Dictionary<string, string> Headers { get; set; }
    public ShoppingBasketData SagaData { get; set; }

    private readonly IEmailService emailService;
    private readonly ICustomerRepository customerRepository;

    public ShoppingBasketSaga(IEmailService emailService, ICustomerRepository customerRepository)
    {
        this.emailService = emailService;
        this.customerRepository = customerRepository;
    }


    public OperationResult Initiate(StartShopping message)
    {
        SagaData.CustomerId = message.CustomerId;
        return new OperationResult(); // no errors to report
    }


    public OperationResult Consume(AddProductIntoBasket message)
    {
        SagaData.BasketProducts.Add(new BasketProducts()
        {
            ProductId = message.ProductId,
            ProductName = message.ProductName,
            ItemCount = message.ItemCount,
            ItemPrice = message.ItemPrice,
        });
        return new OperationResult(); // no possibility to fail
    }


    public OperationResult Consume(NotifyCustomerAboutBasket message)
    {
        var customer = customerRepository.Find(SagaData.CustomerId);
        if (String.IsNullOrEmpty(customer.Email))
        {
            return new OperationResult("No email recorded for the customer - unable to send message");
        }

        try
        {
            var emailMessage = $"We see your basket is not checked-out. We offer you a 85% discount if you go ahead with the checkout. Please visit https://www.example.com/ShoppingBasket/{CorrelationId}";
            emailService.SendEmail(customer.Email, "Checkout not complete", emailMessage);
        }
        catch (Exception exception)
        {
            return new OperationResult($"Failed to send email: {exception}");
        }
        return new OperationResult(); // operation successful
    }
}

And the saga usage will be

    var correlationId = Guid.NewGuid();

    // start the shopping.
    mediator.Consume(new StartShopping()
    {
        CorrelationId = correlationId,
        CustomerId = Guid.NewGuid(),
    });

    // add a product into the basket
    mediator.Consume(new AddProductIntoBasket()
    {
        CorrelationId = correlationId,
        ProductId = 1,
        ProductName = "Magic Dust",
        ItemCount = 42,
        ItemPrice = 42.42M,
    });

There is some documentation and all hosted on GitHub.

Have a look through samples, add a star to the repository and next time you need a multi-step operation, give it a go!