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
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
Use the framework's saga support.
❌ Wrong: skipping retry policy
✅ Correct: explicit retry
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.