NHibernate Custom Saga Finding Logic

Component: NHibernate Persistence | Nuget: NServiceBus.NHibernate (Version: 6.x)
Target NServiceBus Version: 5.x

Code walk-through

When the default Saga message mappings do not satisfy the requirements, custom logic can be put in place to allow NServiceBus to find a saga data instance based on which logic best suites the environment.

NHibernate setup

This sample requires NHibernate persistence package and a running Microsoft SQL Server instance configured accordingly. The sample NHibernate setup can be configured according to the environment:

Edit
var persistence = busConfiguration.UsePersistence<NHibernatePersistence>();
persistence.ConnectionString(@"Data Source=.\SqlExpress;Database=NHCustomSagaFinder;Integrated Security=True");

The Saga

The saga shown in the sample is a very simple order management saga that:

  • Handles the creation of an order.
  • Offloads the payment process to a different handler.
  • Handles the completion of the payment process.
  • Completes the order.
Edit
public class OrderSaga :
    Saga<OrderSagaData>,
    IAmStartedByMessages<StartOrder>,
    IHandleMessages<PaymentTransactionCompleted>,
    IHandleMessages<CompleteOrder>
{
    static ILog log = LogManager.GetLogger<OrderSaga>();

    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderSagaData> mapper)
    {
        // NOP
    }


    public void Handle(StartOrder message)
    {
        Data.OrderId = message.OrderId;
        Data.PaymentTransactionId = Guid.NewGuid().ToString();

        log.Info($"Saga with OrderId {Data.OrderId} received StartOrder with OrderId {message.OrderId}");
        var issuePaymentRequest = new IssuePaymentRequest
        {
            PaymentTransactionId = Data.PaymentTransactionId
        };
        Bus.SendLocal(issuePaymentRequest);
    }

    public void Handle(PaymentTransactionCompleted message)
    {
        log.Info($"Transaction with Id {Data.PaymentTransactionId} completed for order id {Data.OrderId}");
        var completeOrder = new CompleteOrder
        {
            OrderId = Data.OrderId
        };
        Bus.SendLocal(completeOrder);
    }

    public void Handle(CompleteOrder message)
    {
        log.Info($"Saga with OrderId {Data.OrderId} received CompleteOrder with OrderId {message.OrderId}");
        MarkAsComplete();
    }
}

From the process point of view it is important to note that the saga is not sending to the payment processor the order id, instead it is sending a payment transaction id. A saga can be correlated given more than one unique attribute, such as OrderId and PaymentTransactionId, requiring both to be treated as unique.

Edit
class StartOrderSagaFinder :
    IFindSagas<OrderSagaData>.Using<StartOrder>
{
    NHibernateStorageContext storageContext;

    public StartOrderSagaFinder(NHibernateStorageContext storageContext)
    {
        this.storageContext = storageContext;
    }

    public OrderSagaData FindBy(StartOrder message)
    {
        var session = storageContext.Session;
        // if the instance is null a new saga will be automatically created if
        // the Saga handles the message as IAmStartedByMessages<StartOrder>; otherwise an exception is raised.
        return session.QueryOver<OrderSagaData>()
            .Where(d => d.OrderId == message.OrderId)
            .SingleOrDefault();
    }
}

Building a saga finder requires to define a class that implements the IFindSagas<TSagaData>.Using<TMessage> interface. The class will be automatically picked up by NServiceBus at configuration time and used each time a message of type TMessage, that is expected to load a saga of type TSagaData, is received. The FindBy method will be invoked by NServiceBus.

In the sample the implementation of the ConfigureHowToFindSaga method, that is required, is empty since a saga finder is provided for each message type that the saga is handling. It is not required to provide a saga finder for every message type, a mix of standard saga mappings and custom saga finding is a valid scenario.

Related Articles

  • NHibernate Persistence
    NHibernate-based persistence for NServiceBus.
  • Sagas
    NServiceBus uses event-driven architecture to include fault-tolerance and scalability in long-term business processes.

Last modified