Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Service Fabric Persistence Sagas

NuGet Package: NServiceBus.Persistence.ServiceFabric (2.x)
Target Version: NServiceBus 7.x

Reliable collections

Saga data is stored in reliable dictionaries.

Saga data serialization

Saga data is stored in JSON format using Json.NET.

Saga data serialization can be configured by providing a custom JsonSerializerSettings instance.

var persistence = endpointConfiguration.UsePersistence<ServiceFabricPersistence>();

var sagaSettings = persistence.SagaSettings();
sagaSettings.JsonSettings(new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters =
    {
        new IsoDateTimeConverter
        {
            DateTimeStyles = DateTimeStyles.RoundtripKind
        }
    }
});

A custom JsonReader instance:

var persistence = endpointConfiguration.UsePersistence<ServiceFabricPersistence>();

var sagaSettings = persistence.SagaSettings();
sagaSettings.ReaderCreator(
    readerCreator: textReader =>
    {
        return new JsonTextReader(textReader);
    });

Or a custom JsonWriter instance:

var persistence = endpointConfiguration.UsePersistence<ServiceFabricPersistence>();

var sagaSettings = persistence.SagaSettings();
sagaSettings.WriterCreator(
    writerCreator: builder =>
    {
        var writer = new StringWriter(builder);
        return new JsonTextWriter(writer)
        {
            Formatting = Formatting.None
        };
    });

Saga data storage

By default saga data is stored in multiple reliable collections - one per saga data type. Reliable collection names can be changed at the level of saga data type.

[ServiceFabricSaga(CollectionName = "custom-collection-name")]
public class CustomCollectionNameSaga :
    Saga<CustomCollectionNameSaga.SagaData>,
    IHandleMessages<Message>
{
    public Task Handle(Message message, IMessageHandlerContext context)
    {
        return Task.CompletedTask;
    }

The saga data identifier that's used as a key in the reliable dictionary is calculated, amongst others, from the saga data type name. As a result, renaming the saga data class name changes the storage identifier for every saga data instance. This is a problem especially when there are active saga instances stored for a given saga data type. In such scenarios it is necessary to provide a stable saga data type name:

[ServiceFabricSaga(SagaDataName = "saga-data-name")]
public class ExplicitSagaDataNameSaga :
    Saga<ExplicitSagaDataNameSaga.SagaData>,
    IHandleMessages<Message>
{
    public Task Handle(Message message, IMessageHandlerContext context)
    {
        return Task.CompletedTask;
    }

Saga concurrency

When simultaneously handling messages, conflicts may occur. Below is a list of examples of exception that may be thrown Saga concurrency explains how these conflicts are handled, and contains guidance for high-load scenarios.

Creating saga data

Example exception:

The saga with the correlation id 'Name: {correlationProperty.Name} Value: {correlationProperty.Value}' already exists.

Updating or deleting saga data

Starting from version 2.2, ServiceFabric persistence uses LockMode.Update to acquire an exclusive lock when updating or deleting saga data. The saga persister tries to acquire an exclusive lock on the saga data for up to four seconds. If, within this time period, an exclusive lock cannot be acquired, a TimeoutException is thrown and regular message retry policies are applied.

Example exception:

System.TimeoutException: Timed out waiting for Update lock on key; id=730ed849-8996-420f-9abf-e92a6f09585c@132240668520425409@urn:SagaData/dataStore@132240668616392434, timeout=100ms, txn=132240668619482431, lockResourceNameHash=304025969650383958; oldest txn with lock=132240668619502450 (mode Update)

In versions prior to 2.2, ServiceFabric persistence uses optimistic concurrency control when updating or deleting saga data.

Example exception:

{nameof(SagaPersister)} concurrency violation: saga entity Id[{sagaData.Id}] already saved.
This means that the relevant Handle method on the saga will be invoked, even though the message might be later rolled back. Hence it is important to ensure not to perform any work in saga handlers that can't roll back together with the message. This also means that should there be high levels of concurrency there will be N-1 rollbacks where N is the number of concurrent messages. This can cause throughput issues and might require design changes.