Repository: MassTransit/Sample-Outbox License: Apache 2.0 Stack: MassTransit 8, EF Core (PostgreSQL), RabbitMQ, ASP.NET Core, OpenTelemetry/Jaeger
Demonstrates MassTransit's transactional outbox pattern: business data and messages are written in the same database transaction, guaranteeing at-least-once delivery without distributed transactions. Includes a state machine saga that orchestrates a multi-step registration workflow.
Domain: Event registration — a member registers for an event, triggering email notification, attendee tracking, and validation.
- Sample.Api — ASP.NET Core Web API with MVC controller
- Sample.Components — shared library with consumers, saga, services, contracts
- Sample.Worker — background service for consumer processing
- MassTransit — consumers, state machine saga, outbox configuration, consumer definitions
- EF Core — DbContext, entity configurations, saga state mapping, outbox/inbox tables
- Separate worker process — MassTransit required a separate worker for consumer hosting
- WolverineFx.Marten — integrated outbox (messages + documents in same transaction)
- Wolverine handlers — static methods replacing MassTransit consumers
- Cascade messages — handler return values automatically published via outbox
| Aspect | Original (MassTransit) | Converted (Wolverine + Marten) |
|---|---|---|
| Projects | 3 (Api, Components, Worker) | 1 |
| C# files | ~25 | 5 |
| Outbox config | EF Core interceptor + BusOutbox + polling interval | IntegrateWithWolverine() (one line) |
| Saga | State machine class + state entity + definition + map | Handler returning cascade tuple |
| Consumer | IConsumer<T> implementation class + definition class |
Static Handle(T) method |
| Worker process | Separate hosted service | Built into web host |
| Inbox/Outbox tables | 4 MassTransit-managed tables | Wolverine envelope tables (auto-created) |
MassTransit outbox setup (spread across multiple files):
// In each host:
x.AddEntityFrameworkOutbox<RegistrationDbContext>(o => {
o.QueryDelay = TimeSpan.FromSeconds(1);
o.UsePostgres();
o.UseBusOutbox();
});
// Plus: DbContext extends SagaDbContext, outbox tables in migrationsWolverine+Marten outbox setup (one line):
builder.Services.AddMarten(opts => { ... })
.IntegrateWithWolverine(); // That's it. Outbox is built in.The original MassTransit saga required 4 files:
RegistrationStateMachine.cs— state machine definition with transitionsRegistrationState.cs— saga state entityRegistrationStateDefinition.cs— consumer retry and outbox configRegistrationStateMap.cs— EF Core mapping for saga state
The converted version is a single handler method that returns a tuple of cascade messages:
public static (SendRegistrationEmail, AddEventAttendee) Handle(
RegistrationSubmitted message, IDocumentSession session)Wolverine sends both cascade messages via the Marten outbox in the same transaction.
Requires PostgreSQL. Update the connection string in appsettings.json, then:
dotnet runPOST to /registration with:
{ "eventId": "conf-2026", "memberId": "member-1", "payment": 100.00 }Swagger UI available at /swagger.