Skip to main content
Version: 3.2.0

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 =>
{
// Inbound: register a consumer per event this service handles.
wlv.Consume<OrderShipped, OrderShippedHandler>();
wlv.Consume<PaymentProcessed, PaymentProcessedHandler>();

// Outbound: declare each event this service emits (auto-registers the producer).
// wlv.Publish<OrderShipped>();
});

Consume<TEvent, TEventHandler> registers ISubscriber<TEvent> as scoped in the DI container and records the event-to-consumer association in the EventSubscriptionManager. AddSubscriber<TEvent, TEventHandler> is retained as an [Obsolete] alias that forwards to Consume for pre-3.2.0 source compatibility.

An overload accepts a factory delegate when you need more control over how the subscriber is resolved:

wlv.Consume<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

Use Send<TEvent>() for command-style messaging; it auto-registers SendWithWolverineEventProducer:

wlv.Send<OrderShipped>();

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
Consume<TEvent, THandler>RCommon.WolverineExtension on IWolverineEventHandlingBuilder; registers the inbound consumer, handler, and routing (AddSubscriber is the [Obsolete] alias)
RCommonRCommon