Wolverine
RCommon integrates with Wolverine to deliver events both in-process and across service boundaries. The integration registers WolverineEventHandler<TEvent> as a Wolverine IWolverineHandler that delegates to the application's ISubscriber<TEvent>. Handler code has no dependency on Wolverine types.
Installation
dotnet add package RCommon.WolverineWolverine requires host-level registration via UseWolverine() in addition to the RCommon service configuration:
builder.Host.UseWolverine(opts =>
{
// Wolverine transport and endpoint options go here
});
Configuration
Use WithEventHandling<WolverineEventHandlingBuilder> on the RCommon builder:
using RCommon;
using RCommon.Wolverine;
using RCommon.Wolverine.Producers;
builder.Services.AddRCommon()
.WithEventHandling<WolverineEventHandlingBuilder>(wlv =>
{
// Register the producer that publishes events via Wolverine
wlv.AddProducer<PublishWithWolverineEventProducer>();
// Register subscribers and record routing information
wlv.AddSubscriber<OrderShipped, OrderShippedHandler>();
wlv.AddSubscriber<PaymentProcessed, PaymentProcessedHandler>();
});
AddSubscriber<TEvent, TEventHandler> registers ISubscriber<TEvent> as scoped in the DI container and records the event-to-producer association in the EventSubscriptionManager so the router routes this event only to Wolverine producers.
An overload accepts a factory delegate when you need more control over how the subscriber is resolved:
wlv.AddSubscriber<OrderShipped, OrderShippedHandler>(
sp => new OrderShippedHandler(sp.GetRequiredService<IOrderRepository>()));
Defining an event
Events must implement ISerializableEvent. Include a parameterless constructor for Wolverine deserialization:
using RCommon.Models.Events;
public class OrderShipped : ISyncEvent
{
public OrderShipped(Guid orderId, string trackingNumber)
{
OrderId = orderId;
TrackingNumber = trackingNumber;
}
// Required for Wolverine deserialization
public OrderShipped() { }
public Guid OrderId { get; }
public string TrackingNumber { get; } = string.Empty;
}
Implementing a subscriber (handler)
Implement ISubscriber<TEvent>. No Wolverine types appear in handler code:
using RCommon.EventHandling.Subscribers;
public class OrderShippedHandler : ISubscriber<OrderShipped>
{
private readonly ILogger<OrderShippedHandler> _logger;
public OrderShippedHandler(ILogger<OrderShippedHandler> logger)
{
_logger = logger;
}
public async Task HandleAsync(OrderShipped @event, CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Order {OrderId} shipped with tracking {TrackingNumber}",
@event.OrderId, @event.TrackingNumber);
await Task.CompletedTask;
}
}
Producing events
Queue events on IEventRouter and route them after your database work is complete. The router forwards each event to PublishWithWolverineEventProducer, which calls IMessageBus.PublishAsync:
public class ShippingService
{
private readonly IEventRouter _eventRouter;
public ShippingService(IEventRouter eventRouter)
{
_eventRouter = eventRouter;
}
public async Task ShipOrderAsync(Guid orderId, string trackingNumber, CancellationToken cancellationToken)
{
// ... perform database work ...
_eventRouter.AddTransactionalEvent(new OrderShipped(orderId, trackingNumber));
await _eventRouter.RouteEventsAsync(cancellationToken);
}
}
Publish vs Send
Two producers are available:
| Producer | Wolverine call | Use when |
|---|---|---|
PublishWithWolverineEventProducer | IMessageBus.PublishAsync | Fan-out: all handlers subscribed to the type receive the message |
SendWithWolverineEventProducer | IMessageBus.SendAsync | Point-to-point: a single endpoint receives the message |
Register SendWithWolverineEventProducer for command-style messaging:
wlv.AddProducer<SendWithWolverineEventProducer>();
Durable messaging
Wolverine supports durable, transactional messaging through its built-in persistence layer. Configure Wolverine's persistence in the UseWolverine callback:
builder.Host.UseWolverine(opts =>
{
// Use Wolverine's Marten integration for durable messaging (example)
// opts.PersistMessagesWithMarten();
// Or EF Core-based persistence — see the Transactional Outbox page
});
For guaranteed at-least-once delivery using EF Core, see Transactional Outbox.
How the handler bridge works
WolverineEventHandler<TEvent> implements both IWolverineEventHandler<TEvent> and Wolverine's IWolverineHandler. When Wolverine delivers a message it calls HandleAsync(TEvent, CancellationToken), which delegates to the registered ISubscriber<TEvent>:
// Framework code — you do not write this yourself
public class WolverineEventHandler<TEvent> : IWolverineHandler
where TEvent : class, ISerializableEvent
{
public async Task HandleAsync(TEvent @event, CancellationToken cancellationToken = default)
{
await _subscriber.HandleAsync(@event, cancellationToken);
}
}
This keeps application handler code free of any Wolverine API surface.
API summary
| Type | Package | Description |
|---|---|---|
WolverineEventHandlingBuilder | RCommon.Wolverine | Builder used with WithEventHandling<T> to configure Wolverine event handling |
IWolverineEventHandlingBuilder | RCommon.Wolverine | Extends IEventHandlingBuilder with Wolverine-specific subscription capabilities |
PublishWithWolverineEventProducer | RCommon.Wolverine | IEventProducer that calls IMessageBus.PublishAsync (fan-out) |
SendWithWolverineEventProducer | RCommon.Wolverine | IEventProducer that calls IMessageBus.SendAsync (point-to-point) |
WolverineEventHandler<TEvent> | RCommon.Wolverine | Internal IWolverineHandler that bridges Wolverine to ISubscriber<TEvent> |
IWolverineEventHandler<TEvent> | RCommon.Wolverine | Marker interface for Wolverine event handlers |
AddSubscriber<TEvent, THandler> | RCommon.Wolverine | Extension on IWolverineEventHandlingBuilder; registers handler and routing |