Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Simple RavenDB Persistence Usage

NuGet Package: NServiceBus.RavenDB (9.x)
Target Version: NServiceBus 9.x

Code walk-through

This sample shows a simple Client + Server scenario.

  1. Client sends a StartOrder message to Server.
  2. Server starts an OrderSaga.
  3. OrderSaga requests a timeout with a CompleteOrder data.
  4. When the CompleteOrder timeout fires the OrderSaga publishes a OrderCompleted event.
  5. The Server then publishes a message that the client subscribes to.
  6. Client handles OrderCompleted event.

Raven Config

  1. In the RavenDB studio, create a database named RavenSimpleSample. RavenDB does not create the database automatically.
  2. Configure the endpoint to use RavenDB persistence. The URL in the Urls collection may need to be changed to match the database host/port in use.
var endpointConfiguration = new EndpointConfiguration("Samples.RavenDB.Server");
using var documentStore = new DocumentStore
{
    Urls = ["http://localhost:8080"],
    Database = "RavenSimpleSample",
};

documentStore.Initialize();

var persistence = endpointConfiguration.UsePersistence<RavenDBPersistence>();
persistence.SetDefaultDocumentStore(documentStore);

Order Saga Data

public class OrderSagaData :
    ContainSagaData
{
    public Guid OrderId { get; set; }
    public string OrderDescription { get; set; }
}

Order Saga

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

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

    public Task Handle(StartOrder message, IMessageHandlerContext context)
    {
        var orderDescription = $"The saga for order {message.OrderId}";
        Data.OrderDescription = orderDescription;
        log.Info($"Received StartOrder message {Data.OrderId}. Starting Saga");

        var shipOrder = new ShipOrder
        {
            OrderId = message.OrderId
        };

        log.Info("Order will complete in 5 seconds");
        var timeoutData = new CompleteOrder
        {
            OrderDescription = orderDescription
        };

        return Task.WhenAll(
            context.SendLocal(shipOrder),
            RequestTimeout(context, TimeSpan.FromSeconds(5), timeoutData)
        );
    }

    public Task Timeout(CompleteOrder state, IMessageHandlerContext context)
    {
        log.Info($"Saga with OrderId {Data.OrderId} completed");
        var orderCompleted = new OrderCompleted
        {
            OrderId = Data.OrderId
        };
        MarkAsComplete();
        return context.Publish(orderCompleted);
    }
}

Handler Using RavenDB Session

The handler access's the same RavenDB ISession via ISessionProvider.

public class ShipOrderHandler :
    IHandleMessages<ShipOrder>
{
    public Task Handle(ShipOrder message, IMessageHandlerContext context)
    {
        var session = context.SynchronizedStorageSession.RavenSession();
        var orderShipped = new OrderShipped
        {
            OrderId = message.OrderId,
            ShippingDate = DateTime.UtcNow,
        };
        return session.StoreAsync(orderShipped, context.CancellationToken);
    }
}

The Data in RavenDB

The data in RavenDB is stored in three different collections.

The Saga Data

  • IContainSagaData.Id maps to the native RavenDB document Id.
  • IContainSagaData.Originator and IContainSagaData.OriginalMessageId map to simple properties.
  • Custom properties on the SagaData, in this case OrderDescription and OrderId, are also mapped to simple properties.
{
    "IdentityDocId": "OrderSagaData/OrderId/33e54adb-10fe-ac05-abdd-a656e8f995b3",
    "Data": {
        "$type": "OrderSagaData, Server",
        "OrderId": "683e7b20-527a-475d-847c-79ef6b0f40a1",
        "OrderDescription": "The saga for order 683e7b20-527a-475d-847c-79ef6b0f40a1",
        "Id": "9bf646a0-25b1-4a79-afe3-adf600965476",
        "Originator": "Samples.RavenDB.Client",
        "OriginalMessageId": "06cdd874-de9b-40bb-8b90-adf600965448"
    },
    "@metadata": {
        "@collection": "SagaDataContainers",
        "Raven-Clr-Type": "NServiceBus.Persistence.RavenDB.SagaDataContainer, NServiceBus.RavenDB",
        "NServiceBus-Persistence-RavenDB-SagaDataContainer-SchemaVersion": "1.0.0"
    }
}

The Handler Stored data

{
    "OrderId": "683e7b20-527a-475d-847c-79ef6b0f40a1",
    "ShippingDate": "2021-12-06T09:07:20.1902988Z",
    "@metadata": {
        "@collection": "OrderShippeds",
        "Raven-Clr-Type": "OrderShipped, Server"
    }
}

Related Articles

  • RavenDB Persistence
  • Sagas
    Maintain statefulness in distributed systems with the saga pattern and NServiceBus' event-driven architecture with built-in fault-tolerance and scalability.