PANiXiDA.Core.Infrastructure.Messaging.Wolverine 4.1.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package PANiXiDA.Core.Infrastructure.Messaging.Wolverine --version 4.1.2
                    
NuGet\Install-Package PANiXiDA.Core.Infrastructure.Messaging.Wolverine -Version 4.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.2" />
                    
Directory.Packages.props
<PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PANiXiDA.Core.Infrastructure.Messaging.Wolverine --version 4.1.2
                    
#r "nuget: PANiXiDA.Core.Infrastructure.Messaging.Wolverine, 4.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PANiXiDA.Core.Infrastructure.Messaging.Wolverine@4.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PANiXiDA.Core.Infrastructure.Messaging.Wolverine&version=4.1.2
                    
Install as a Cake Addin
#tool nuget:?package=PANiXiDA.Core.Infrastructure.Messaging.Wolverine&version=4.1.2
                    
Install as a Cake Tool

PANiXiDA.Core.Infrastructure.Messaging.Wolverine

PANiXiDA.Core.Infrastructure.Messaging.Wolverine is a .NET library that connects PANiXiDA.Core application messaging abstractions to WolverineFx.

It provides an in-process mediator, in-process domain event publishing by default, optional Kafka topic routing for selected event types, and durable inbox/outbox support backed by PostgreSQL.

Status

CI NuGet NuGet downloads Target Framework License

Features

  • IMediator implementation based on Wolverine in-process invocation.
  • IEventBus implementation based on Wolverine EF Core outbox.
  • IScheduler implementation for durable command and event scheduling through the same outbox.
  • Default in-process event handling for domain events.
  • Explicit Kafka producer and consumer registration per event type.
  • Durable inbox and outbox policies for listeners, local queues, and external senders.
  • PostgreSQL message storage with EF Core transaction integration.
  • One shared Wolverine runtime and message-store schema across multiple module DbContexts.
  • Request-scoped routing to keyed module IUnitOfWork and EF Core outbox services.
  • FluentValidation validator registration from Wolverine discovery assemblies.
  • Wolverine application assembly resolution from the entry assembly for pre-generated handler code.
  • Runtime compilation support for Wolverine TypeLoadMode.Auto.

Quick Start

Requirements

  • .NET 10 SDK
  • PostgreSQL for Wolverine message storage
  • Kafka only when external event topics are registered

Full Native AOT support is not currently provided.

Installation

Use the latest 4.1.x version:

<ItemGroup>
  <PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.*" />
</ItemGroup>

Minimal Setup

using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;

builder.Services.AddWolverineMediator<AppDbContext>();

builder.Host.UseWolverineMediator<AppDbContext>(
    builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
    typeof(CreateUserHandler).Assembly);

AddWolverineMediator<TDbContext>() registers PANiXiDA IMediator, IEventBus, IScheduler, and the EF Core outbox dispatcher.

UseWolverineMediator<TDbContext>() configures Wolverine, PostgreSQL message storage, EF Core transactions, request middleware, FluentValidation validators from discovery assemblies, durable local queues, durable inbox, and durable outbox.

The package sets Wolverine's application assembly to the process entry assembly. This keeps generated handler code in the publishable application assembly when using Wolverine code generation commands such as dotnet run -- codegen write.

The package uses Wolverine TypeLoadMode.Auto and includes Wolverine runtime compilation support for handler code that has not been pre-generated.

Modular Setup

Use the non-generic overload when a host contains multiple modules with independent write DbContexts:

using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;

builder.Host.UseWolverineMediator(
    builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
    modules =>
    {
        modules.AddModule<IdentityWriteDbContext>(
            typeof(CreateUserCommand).Assembly,
            typeof(IdentityIntegrationEventHandler).Assembly);
        modules.AddModule<OrdersWriteDbContext>(
            typeof(CreateOrderCommand).Assembly,
            typeof(OrdersIntegrationEventHandler).Assembly);
    });

This overload registers one IMediator, one IEventBus, one IScheduler, and one Wolverine runtime. Both DbContexts are enrolled in the same PostgreSQL message store and therefore use the same durable inbox/outbox tables in the wolverine schema.

Each AddModule<TDbContext>() also creates a scoped IOutboxDispatcher registration keyed by typeof(TDbContext). Outside the mediator pipeline, resolve it from the same DI scope as the module's DbContext:

using Microsoft.Extensions.DependencyInjection;
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.OutboxDispatcher;

var outbox = scope.ServiceProvider.GetRequiredKeyedService<IOutboxDispatcher>(
    typeof(OrdersWriteDbContext));

This selects the module's EF Core outbox directly. Within an explicit transaction, call outbox.SaveChangesAsync(cancellationToken) before commit and outbox.FlushAsync(cancellationToken) after commit.

Handlers for the same event type are separated into independent local queues and transactions. Durable message identity includes the destination so fan-out handlers have independent inbox records, retries, and failure handling.

The first assembly passed to AddModule<TDbContext>() contains the module's requests and is owned by exactly one DbContext. Additional assemblies are used only for handler and validator discovery. Assigning one request assembly to different DbContexts is rejected during configuration.

The module persistence registration must expose keyed IUnitOfWork services under the corresponding DbContext types. PANiXiDA.Core.Infrastructure.Persistence.Ef does this automatically for write DbContexts. During a mediator request, the package activates the owning module and routes the existing non-generic IUnitOfWork and IEventBus abstractions to that module's transaction and EF Core outbox.

Before a successful command commits, PersistOutgoingMessagesBehavior saves the active module's DbContext, including tracked message envelopes and business changes. It runs after domain event publication and before CommitTransactionBehavior; FlushOutgoingMessagesBehavior releases messages only after commit. Custom request pipelines must preserve this order. IUnitOfWork.CommitTransactionAsync() itself only completes the transaction.

Custom IOutboxDispatcher implementations must implement SaveChangesAsync(CancellationToken cancellationToken) when upgrading. Persist pending messages before commit, or explicitly return a completed task if they were already persisted in the current transaction. No default implementation is provided by the interface.

They must also implement SendAsync and the PublishAsync overload accepting Wolverine DeliveryOptions. Preserve those options and add messages to the same transactional outbox without committing or flushing it.

Calls to SaveChangesAsync and FlushAsync require an explicit cancellation token. Pass CancellationToken.None when cancellation is not needed.

For ordinary Wolverine messages, including events, module selection is not based on the message assembly. Wolverine detects the transaction from the concrete handler's DbContext dependency. This allows handlers for one shared event contract to commit or roll back independently in different module schemas. A transactional handler must depend on exactly one write DbContext.

IEventBus and IScheduler share IOutboxDispatcher, which uses the keyed module outbox inside the mediator request pipeline and the current Wolverine message context inside native handlers. The inbox record, handler changes, and messages published or scheduled by a native handler therefore share its selected DbContext transaction.

Without an active mediator module, the modular dispatcher's SaveChangesAsync throws InvalidOperationException: it cannot select a DbContext to save. Native handlers rely on Wolverine's transaction middleware to save changes and flush messages, so FlushAsync remains a no-op in that context. Outside the mediator pipeline, use the keyed module dispatcher to save and flush explicitly.

Do not synchronously invoke a command from another module while the first module transaction is active. Separate DbContexts use separate local database transactions, so such a call cannot be atomic. Publish an event through the outbox and let the receiving module handle it independently.

Scheduling

Inject IScheduler from PANiXiDA.Core.Application.Messaging.Scheduling into handlers:

await scheduler.ScheduleAsync(command, TimeSpan.FromMinutes(15), cancellationToken);
await scheduler.ScheduleAtAsync(occurredEvent, publishAt, cancellationToken);

Commands use SendAsync; events use PublishAsync. Scheduling delays delivery and does not return handler results. The adapters do not check messages or delivery options for null, reject negative delays, or check cancellation before dispatch. Delay values are passed unchanged to Wolverine. Persistence still passes the cancellation token to EF Core. Inside mediator requests, scheduled messages use the active module's EF Core outbox and commit or roll back with its business changes. Native Wolverine handlers use their enlisted message context. The configured PostgreSQL storage and durable queues preserve scheduled messages across restarts.

Outside handlers in modular setups, construct WolverineScheduler with the module's keyed IOutboxDispatcher resolved using typeof(AppDbContext). Single-context setups can use the registered IScheduler. Save and commit through the same IDbContextOutbox<AppDbContext>/DbContext transaction; scheduling alone does not commit changes. The adapter uses typed DI registrations and introduces no runtime reflection or code generation; full Native AOT support remains limited by Wolverine and EF Core.

Kafka Topics

Kafka is opt-in per event type. If no Kafka producer is registered for an event, publishing stays in-process.

Create typed option models in the consuming infrastructure project:

using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.Options;

public sealed class MainKafkaBrokerOption : KafkaBrokerOption
{
}

public sealed class UserCreatedKafkaProducerOption : KafkaProducerOption
{
}

public sealed class UserCreatedKafkaConsumerOption : KafkaConsumerOption
{
}

Register brokers, producers, and consumers in the Wolverine mediator configuration:

using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;

builder.Services.AddWolverineMediator<AppDbContext>();

builder.Host.UseWolverineMediator<AppDbContext>(
    builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
    builder.Configuration,
    options =>
    {
        options.AddKafkaBroker<MainKafkaBrokerOption>();
        options.AddKafkaProducer<UserCreatedKafkaProducerOption, UserCreated>();
        options.AddKafkaConsumer<UserCreatedKafkaConsumerOption, UserCreated>();
    },
    typeof(UserCreatedHandler).Assembly);

Configuration sections are resolved by option type name:

{
  "ConnectionStrings": {
    "PostgreSqlConnectionString": "Host=localhost;Port=5432;Database=app;Username=app;Password=app"
  },
  "MainKafkaBrokerOption": {
    "BootstrapServers": "localhost:9092"
  },
  "UserCreatedKafkaProducerOption": {
    "TopicName": "users.created"
  },
  "UserCreatedKafkaConsumerOption": {
    "TopicName": "users.created",
    "ConsumerGroupId": "users-service",
    "AutoOffsetReset": "Earliest"
  }
}

For named Kafka brokers, put the broker name into broker and topic options:

public sealed class ExternalKafkaBrokerOption : KafkaBrokerOption
{
}

public sealed class ExternalUserCreatedKafkaProducerOption : KafkaProducerOption
{
}
{
  "ExternalKafkaBrokerOption": {
    "BrokerName": "external",
    "BootstrapServers": "external-kafka:9092"
  },
  "ExternalUserCreatedKafkaProducerOption": {
    "BrokerName": "external",
    "TopicName": "external.users.created"
  }
}
options.AddKafkaBroker<ExternalKafkaBrokerOption>();
options.AddKafkaProducer<ExternalUserCreatedKafkaProducerOption, UserCreated>();

EF Core Storage

The package enrolls TDbContext in Wolverine PostgreSQL message storage. If the application keeps Wolverine envelope tables in EF Core migrations, map them in the DbContext model:

using Wolverine.EntityFrameworkCore;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.MapWolverineEnvelopeStorage("wolverine");
}

With the modular overload, the configured message-store schema is shared by all modules. Let Wolverine managed resources own that schema, or map it in exactly one dedicated messaging migration context. Do not map the shared envelope tables in every business module DbContext.

Dead Letter Expiration

Both single-context and modular registrations enable automatic expiration of database-backed dead letters after seven days. Wolverine assigns the expiration when a message enters the dead letter store: it uses the message's DeliverBy value when present, otherwise the current time plus seven days. Its background durability agent permanently deletes expired messages; cleanup is periodic, not immediate at the expiration time.

Applications can override the retention period or disable expiration by configuring Wolverine after UseWolverineMediator:

using Wolverine;

builder.Services.ConfigureWolverine(options =>
{
    options.Durability.DeadLetterQueueExpirationEnabled = true;
    options.Durability.DeadLetterQueueExpiration = TimeSpan.FromDays(14);
});

Set DeadLetterQueueExpirationEnabled to false to disable automatic cleanup. These settings apply to database-backed dead letters, not native broker dead letter queues. Existing rows without an expiration timestamp are not backfilled by this configuration change.

Behavior

Commands and queries are invoked in-process through Wolverine and PANiXiDA request contracts.

Domain events are published through IEventBus. By default, Wolverine dispatches them to local handlers. When a Kafka producer is registered for the event type, the same event is also routed to the configured Kafka topic through durable outbox.

Kafka consumers use durable inbox and map incoming topic messages to the configured event type with DefaultIncomingMessage<TEvent>().

Request Behaviors

The default request behavior pipeline is:

before:  ValidationBehavior
before:  BeginTransactionBehavior
after:   PublishDomainEventsBehavior
after:   PersistOutgoingMessagesBehavior
after:   CommitTransactionBehavior
after:   FlushOutgoingMessagesBehavior
finally: CleanupTransactionBehavior

The modular overload activates module routing before validation, keeps the application CleanupTransactionBehavior, and releases module routing after cleanup. The application-facing pipeline continues to depend only on the PANiXiDA IUnitOfWork and IEventBus abstractions.

Validators are discovered from the same assemblies passed to UseWolverineMediator<TDbContext>() for handler discovery.

The bundled source generator prepares request behavior metadata and scoped FluentValidation registrations. Keep analyzer assets enabled in the host and reference the assemblies passed to AddModule or UseWolverineMediator. Requests, behaviors, validators, and validated types must be accessible to generated code, normally public for referenced assemblies. Internal validators require the generator in their declaring project. No additional registration calls, attributes, partial declarations, or code-generation commands are required.

Missing validator registrations fail explicitly at startup without reflection scanning. Validators within each discovery assembly are registered in deterministic type-name order. Rebuild and republish the host together with changed discovery assemblies to regenerate their validator registrations. Validators emitted by another source generator must be compiled in a referenced project, such as Application, so the host can discover them. Generating them alongside Wolverine registrations in the same project produces diagnostic PANWOLVSG001.

Custom behaviors can be appended or inserted before or after any behavior in the same stage:

using PANiXiDA.Core.Application.Messaging.Mediator.Behaviors;
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.Behaviors;

builder.Host.UseWolverineMediator<AppDbContext>(
    builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
    behaviors =>
    {
        behaviors.Before.InsertAfter(
            typeof(AuthorizeRequestBehavior<,>),
            typeof(ValidationBehavior<,>));

        behaviors.After.InsertBefore(
            typeof(AuditRequestResultBehavior<,>),
            typeof(CommitTransactionBehavior<,>));

        behaviors.Finally.InsertAfter(
            typeof(ReleaseRequestLockBehavior<,>),
            typeof(CleanupTransactionBehavior<,>));
    },
    typeof(CreateUserHandler).Assembly);

The same behavior configuration can be combined with Kafka topology registration:

builder.Host.UseWolverineMediator<AppDbContext>(
    builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
    builder.Configuration,
    kafka =>
    {
        kafka.AddKafkaBroker<MainKafkaBrokerOption>();
        kafka.AddKafkaProducer<UserCreatedKafkaProducerOption, UserCreated>();
    },
    behaviors =>
    {
        behaviors.After.InsertBefore(
            typeof(AuditRequestResultBehavior<,>),
            typeof(FlushOutgoingMessagesBehavior<,>));
    },
    typeof(UserCreatedHandler).Assembly);

Development

dotnet restore
dotnet format
dotnet build --configuration Release
dotnet test --configuration Release

Continuous integration

Every pull request and push to main runs formatting, tests, and mandatory SonarQube analysis. Publishing from main starts only after the SonarQube Quality Gate succeeds.

License

This project is licensed under the Apache-2.0 license.

See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.0.3 193 11/5/2026
6.0.2 933 11/3/2026
6.0.1 65 11/3/2026
5.1.3 530 10/21/2026
5.1.2 123 10/20/2026
5.1.1 187 10/18/2026
5.0.3 130 10/11/2026
5.0.2 128 10/11/2026
5.0.1 144 10/9/2026
4.2.4 94 10/5/2026
4.2.3 100 10/5/2026
4.2.2 97 10/5/2026
4.2.1 195 10/4/2026
4.1.2 130 10/4/2026
4.1.1 103 10/3/2026
4.0.2 108 9/27/2026
4.0.1 106 9/27/2026
3.0.2 327 8/30/2026
3.0.1 114 8/29/2026
2.0.5 132 8/29/2026
Loading failed