Skip to main content
Version: 2.4.1

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

NuGet Package
dotnet add package RCommon.Wolverine

Wolverine 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:

ProducerWolverine callUse when
PublishWithWolverineEventProducerIMessageBus.PublishAsyncFan-out: all handlers subscribed to the type receive the message
SendWithWolverineEventProducerIMessageBus.SendAsyncPoint-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

TypePackageDescription
WolverineEventHandlingBuilderRCommon.WolverineBuilder used with WithEventHandling<T> to configure Wolverine event handling
IWolverineEventHandlingBuilderRCommon.WolverineExtends IEventHandlingBuilder with Wolverine-specific subscription capabilities
PublishWithWolverineEventProducerRCommon.WolverineIEventProducer that calls IMessageBus.PublishAsync (fan-out)
SendWithWolverineEventProducerRCommon.WolverineIEventProducer that calls IMessageBus.SendAsync (point-to-point)
WolverineEventHandler<TEvent>RCommon.WolverineInternal IWolverineHandler that bridges Wolverine to ISubscriber<TEvent>
IWolverineEventHandler<TEvent>RCommon.WolverineMarker interface for Wolverine event handlers
AddSubscriber<TEvent, THandler>RCommon.WolverineExtension on IWolverineEventHandlingBuilder; registers handler and routing
RCommonRCommon