RavenDb Integration 3.0
Wolverine supports a RavenDb backed message persistence strategy option as well as RavenDb-backed transactional middleware and saga persistence. To get started, add the WolverineFx.RavenDb
dependency to your application:
dotnet add package WolverineFx.RavenDb
and in your application, tell Wolverine to use RavenDb for message persistence:
var builder = Host.CreateApplicationBuilder();
// You'll need a reference to RavenDB.DependencyInjection
// for this one
builder.Services.AddRavenDbDocStore(raven =>
{
// configure your RavenDb connection here
});
builder.UseWolverine(opts =>
{
// That's it, nothing more to see here
opts.UseRavenDbPersistence();
// The RavenDb integration supports basic transactional
// middleware just fine
opts.Policies.AutoApplyTransactions();
});
// continue with your bootstrapping...
Also see RavenDb's own documentation for bootstrapping RavenDb inside of a .NET application.
Message Persistence
The durable inbox and outbox options in Wolverine are completely supported with RavenDb as the persistence mechanism. This includes scheduled execution (and retries), dead letter queue storage using the DeadLetterMessage
collection, and the ability to replay designated messages in the dead letter queue storage.
Saga Persistence
The RavenDb integration can serve as a Wolverine Saga persistence mechanism. The only limitation is that your Saga
types can only use strings as the identity for the Saga
.
public class Order : Saga
{
// Just use this for the identity
// of RavenDb backed sagas
public string Id { get; set; }
// Handle and Start methods...
}
There's nothing else to do, if RavenDb integration is applied to your Wolverine, it's going to kick in for saga persistence as long as your Saga
type has a string identity property.
Transactional Middleware
WARNING
The RavenDb transactional middleware only supports the RavenDb IAsyncDocumentSession
service
The normal configuration options for transactional middleware in Wolverine apply to the RavenDb backend, so either mark handlers explicitly with [Transactional]
like so:
public class CreateDocCommandHandler
{
[Transactional]
public async Task Handle(CreateDocCommand message, IAsyncDocumentSession session)
{
await session.StoreAsync(new FakeDoc { Id = message.Id });
}
}
Or if you choose to do this more conventionally (which folks do tend to use quite often):
builder.UseWolverine(opts =>
{
// That's it, nothing more to see here
opts.UseRavenDbPersistence();
// The RavenDb integration supports basic transactional
// middleware just fine
opts.Policies.AutoApplyTransactions();
});
and the transactional middleware will kick in on any message handler or HTTP endpoint that uses the RavenDb IAsyncDocumentSession
like this handler signature:
public class AlternativeCreateDocCommandHandler
{
// Auto transactions would kick in just because of the dependency
// on IAsyncDocumentSession
public async Task Handle(CreateDocCommand message, IAsyncDocumentSession session)
{
await session.StoreAsync(new FakeDoc { Id = message.Id });
}
}
The transactional middleware will also be applied for any usage of the RavenOps
side effects model for Wolverine's RavenDb integration:
public record RecordTeam(string Team, int Year);
public static class RecordTeamHandler
{
public static IRavenDbOp Handle(RecordTeam command)
{
return RavenOps.Store(new Team { Id = command.Team, YearFounded = command.Year });
}
}
System Control Queues
The RavenDb integration to Wolverine does not yet come with a built in database control queue mechanism, so you will need to add that from external messaging brokers as in this example using Azure Service Bus:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// One way or another, you're probably pulling the Azure Service Bus
// connection string out of configuration
var azureServiceBusConnectionString = builder
.Configuration
.GetConnectionString("azure-service-bus")!;
// Connect to the broker in the simplest possible way
opts.UseAzureServiceBus(azureServiceBusConnectionString)
.AutoProvision()
// This enables Wolverine to use temporary Azure Service Bus
// queues created at runtime for communication between
// Wolverine nodes
.EnableWolverineControlQueues();
});
For local development, there is also an option to let Wolverine just use its TCP transport as a control endpoint with this configuration option:
WolverineOptions.UseTcpForControlEndpoint();
In the option above, Wolverine is just looking for an unused port, and assigning that found port as the listener for the node being bootstrapped.
RavenOps Side Effects
The RavenOps
static class can be used as a convenience for RavenDb integration with Wolverine:
/// <summary>
/// Side effect helper class for Wolverine's integration with RavenDb
/// </summary>
public static class RavenOps
{
/// <summary>
/// Store a new RavenDb document
/// </summary>
/// <param name="document"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IRavenDbOp Store<T>(T document) => new StoreDoc<T>(document);
/// <summary>
/// Delete this document in RavenDb
/// </summary>
/// <param name="document"></param>
/// <returns></returns>
public static IRavenDbOp DeleteDocument(object document) => new DeleteByDoc(document);
/// <summary>
/// Delete a RavenDb document by its id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static IRavenDbOp DeleteById(string id) => new DeleteById(id);
}
See the Wolverine side effects model for more information.