Skip to content

MassTransit & Wolverine

Key Points

  • MassTransit and Wolverine are .NET messaging frameworks abstracting over brokers (RabbitMQ, Service Bus, Azure Storage, Kafka via Riders, in-memory).
  • MassTransit v8 became commercially licensed in 2025 — paid for production. Wolverine by Jeremy Miller is the community alternative.
  • Both provide: consumers, sagas, outbox, retry, scheduling, request-response, test harness. Worth using for any non-trivial messaging.
  • Choose MassTransit if you have a license / love the ecosystem. Choose Wolverine for free + modern source-gen approach + workflow-style features.
  • Skip them for simplest possible cases — direct broker SDKs work.

Concepts (deep dive)

MassTransit setup

builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderPlacedConsumer>();
    x.AddSagaStateMachine<OrderSagaMachine, OrderSagaState>();

    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("rabbitmq", "/", h => { h.Username("guest"); h.Password("guest"); });
        cfg.ConfigureEndpoints(ctx);
        cfg.UseMessageRetry(r => r.Intervals(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)));
    });
});
public class OrderPlacedConsumer : IConsumer<OrderPlaced>
{
    public Task Consume(ConsumeContext<OrderPlaced> ctx) => /* ... */;
}

Convention: consumer named XConsumer for message X.

MassTransit features

  • Sagas: state machines (Automatonymous-based).
  • Outbox: AddEntityFrameworkOutbox<TDb>().
  • Scheduling: schedule messages for future delivery.
  • Request-reply: IRequestClient<T> for async RPC.
  • Test harness: in-memory testing.
  • OpenTelemetry: built-in.
  • Retry policies: per-consumer or per-pipeline.
  • Job consumers: long-running with state.

Request-reply

public class GetOrderClient(IRequestClient<GetOrder> client)
{
    public async Task<Order> Get(Guid id)
    {
        var resp = await client.GetResponse<Order>(new GetOrder(id));
        return resp.Message;
    }
}

Wolverine setup

builder.Host.UseWolverine(o =>
{
    o.UseRabbitMq()
        .DeclareExchange("orders")
        .BindQueue("orders.processed", "orders");

    o.PublishMessage<OrderPlaced>().ToRabbitExchange("orders");

    o.Discovery.IncludeAssembly(typeof(Program).Assembly);
});

Convention discovery picks up handlers:

public static class OrderHandler
{
    public static async Task Handle(OrderPlaced msg, AppDb db)
    {
        // ...
    }
}

Static handlers; minimal ceremony.

Wolverine features

  • Source-gen runtime: handlers compiled to optimized code; very fast.
  • Sagas: simple class-based.
  • Outbox/inbox: built-in (Marten or EF).
  • Scheduling: Bus.Schedule(...).
  • Request-reply.
  • Workflow-style: cascaded messages from handler return values.
  • Local in-memory: same handler API for in-process and over-broker.

Cascading messages

public static OrderPlaced Handle(PlaceOrder cmd, AppDb db)
{
    var order = new Order(cmd);
    db.Orders.Add(order);
    return new OrderPlaced(order.Id);   // automatically published
}

Returning a message from a handler auto-publishes. Concise; reduces boilerplate.

Outbox in Wolverine

.UseEntityFrameworkCoreTransactions()    // enables outbox
.IntegrateWithMarten()                    // or Marten

Each message published from a handler scope is queued in outbox and committed with the DB transaction.

Retry policies

MassTransit:

cfg.UseMessageRetry(r =>
    r.Exponential(5, TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(2)));
cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15)));

Wolverine:

o.OnException<TimeoutException>().RetryWithCooldown(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
o.OnException<HttpRequestException>().MoveToErrorQueue();

Sagas (state machines)

See Sagas: Orchestration vs Choreography. MassTransit and Wolverine both support sagas with persistence.

Test harness

// MassTransit
[Fact]
public async Task Test_consumer()
{
    await using var provider = new ServiceCollection()
        .AddMassTransitTestHarness(x => x.AddConsumer<OrderPlacedConsumer>())
        .BuildServiceProvider(true);

    var harness = provider.GetRequiredService<ITestHarness>();
    await harness.Start();

    await harness.Bus.Publish(new OrderPlaced(orderId));

    Assert.True(await harness.Consumed.Any<OrderPlaced>());
    var consumer = harness.GetConsumerHarness<OrderPlacedConsumer>();
    Assert.True(await consumer.Consumed.Any<OrderPlaced>());
}

In-memory bus; no broker. Fast.

Headers and correlation

Both auto-propagate traceparent, MessageId, CorrelationId. Custom headers via API.

Choosing

Criterion MassTransit Wolverine
License Commercial (v8+) Free (MIT)
Maturity Older; massive ecosystem Newer; smaller
Performance Good Source-gen; fastest .NET
API Conventional consumer classes Static handlers
Outbox/inbox EF, Mongo EF, Marten
Saga State machine fluent Class-based
Workflow Job consumers Cascading messages
Community Large Growing

For 2026 new projects: Wolverine is appealing for cost + perf. MassTransit if license fine + ecosystem bias.

When NOT use either

  • Single broker, simple consumer pattern, no sagas → use the broker SDK directly. MassTransit/Wolverine add weight.
  • Hot-path messaging where every microsecond counts → benchmark both vs raw client.

Migration

Both support gradual adoption. Run alongside raw SDK code; migrate one consumer at a time.

Aspire integration

.NET Aspire has integrations for both. Local dev experience is solid — declare broker resource in AppHost; SDK auto-configures.


Code: correct vs wrong

❌ Wrong: hand-rolling sagas

// State stored ad-hoc; transitions in if-statements

Use the framework's saga support.

❌ Wrong: skipping retry policy

cfg.ConfigureEndpoints(ctx);   // no retry → first failure → DLQ

✅ Correct: explicit retry

cfg.UseMessageRetry(r => r.Exponential(...));

Design patterns for this topic

Pattern 1 — "Framework over raw SDK"

  • Intent: sagas, outbox, retry, test harness.

Pattern 2 — "Cascading messages (Wolverine)"

  • Intent: concise event publishing.

Pattern 3 — "Test harness for fast tests"

  • Intent: no broker required.

Pattern 4 — "Outbox via integration"

  • Intent: atomic DB + send.

Pattern 5 — "OpenTelemetry built-in"

  • Intent: zero-config tracing.

Pros & cons / trade-offs

Aspect MassTransit Wolverine
License Commercial MIT
Ecosystem Vast Growing
Perf Good Best
API style Class-based Static-friendly

When to use / when to avoid

  • Use for sagas, outbox, multi-broker.
  • Use Wolverine for cost-sensitive new projects.
  • Avoid for simplest fire-and-forget cases.

Interview Q&A

Q1. Why MassTransit/Wolverine over raw broker SDK? Saga, outbox, retry, request-reply, test harness, OTel — bundled abstractions.

Q2. License model in MassTransit v8? Commercial for production. Wolverine is free (MIT).

Q3. Wolverine's main innovation? Source-gen runtime: handlers compiled to direct method calls; very fast.

Q4. Cascading messages? Wolverine: handler returns a message → auto-published. Reduces boilerplate.

Q5. Test harness? In-memory bus; no broker; fast tests of consumers/sagas.

Q6. Outbox setup? AddEntityFrameworkOutbox (MassTransit) / UseEntityFrameworkCoreTransactions (Wolverine).

Q7. Retry policy default? None. Configure explicitly — exponential backoff to a max; then DLQ.

Q8. Request-reply? IRequestClient<T>.GetResponse<TResp>(...). Async RPC over messaging.

Q9. Sagas in MassTransit? State machines via Automatonymous fluent API. Persisted to DB.

Q10. Sagas in Wolverine? Simple class-based. Persisted to Marten or EF.

Q11. Switching brokers? Both abstract — change one builder. But schema/topology choices may differ.

Q12. Performance comparison? Wolverine source-gen edge in raw throughput. Both fast enough for most apps.


Gotchas / common mistakes

  • ⚠️ No retry policy — instant DLQ.
  • ⚠️ MassTransit license violation in production.
  • ⚠️ Sagas without optimistic concurrency.
  • ⚠️ Outbox not configured in DI — silent failure.
  • ⚠️ Non-idempotent consumers — duplicates on retry.

Further reading