RavenDB's implementation of distributed transactions contains a bug that could cause an endpoint, in certain (rare) conditions, to lose data. If RavenDB is configured to enlist in distributed transactions, read DTC not supported for RavenDB Persistence.
Code walk-through
This sample shows a simple Client + Server scenario.
Client
sends aStartOrder
message toServer
.Server
starts anOrderSaga
.OrderSaga
requests a timeout with aCompleteOrder
data.- When the
CompleteOrder
timeout fires theOrderSaga
publishes aOrderCompleted
event. - The Server then publishes a message that the client subscribes to.
Client
handlesOrderCompleted
event.
Raven Config
- In the RavenDB studio, create a database named
RavenSimpleSample
. RavenDB does not create the database automatically. - 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 = new[] { "http://localhost:8080" },
Database = "RavenSimpleSample",
})
{
documentStore.Initialize();
var persistence = endpointConfiguration.UsePersistence<RavenDBPersistence>();
// Only required to simplify the sample setup
persistence.SetDefaultDocumentStore(documentStore);
When the RavenDB
DocumentStore
is created by the user at endpoint configuration time it's important to dispose it, by calling the Dispose()
method, before shutting down the endpoint process.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.ConfigureMapping<StartOrder>(message => message.OrderId)
.ToSaga(sagaData => sagaData.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 Raven Session
The handler access the same Raven 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);
}
}
The Data in RavenDB
The data in RavenDB is stored in three different collections.
By default, this sample uses the Learning Transport, which has built-in support for timeouts and subscriptions. To see the data for timeouts and subscriptions, it's necessary to change the sample to a different transport that does not have these native features such as MSMQ.
The Saga Data
IContainSagaData.
maps to the native RavenDB documentId Id
.IContainSagaData.
andOriginator IContainSagaData.
map to simple properties.OriginalMessageId - Custom properties on the SagaData, in this case
OrderDescription
andOrderId
, are also mapped to simple properties.
The Timeouts
- The subscriber is stored in a
Destination
with the nested propertiesQueue
andMachine
. - The endpoint that initiated the timeout is stored in the
OwningTimeoutManager
property. - The connected saga ID is stored in a
SagaId
property. - The serialized data for the message is stored in a
State
property. - The scheduled timestamp for the timeout is stored in a
Time
property. - Any headers associated with the timeout are stored in an array of key value pairs.
The Subscriptions
Note that the message type maps to multiple subscriber endpoints.
- The Subscription message type and version are stored in the
MessageType
property. - The list of subscribers is stored in a array of objects each containing
Queue
andMachineName
properties.