ZeroAlloc.Saga.Outbox 4.1.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package ZeroAlloc.Saga.Outbox --version 4.1.4
                    
NuGet\Install-Package ZeroAlloc.Saga.Outbox -Version 4.1.4
                    
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="ZeroAlloc.Saga.Outbox" Version="4.1.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZeroAlloc.Saga.Outbox" Version="4.1.4" />
                    
Directory.Packages.props
<PackageReference Include="ZeroAlloc.Saga.Outbox" />
                    
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 ZeroAlloc.Saga.Outbox --version 4.1.4
                    
#r "nuget: ZeroAlloc.Saga.Outbox, 4.1.4"
                    
#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 ZeroAlloc.Saga.Outbox@4.1.4
                    
#: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=ZeroAlloc.Saga.Outbox&version=4.1.4
                    
Install as a Cake Addin
#tool nuget:?package=ZeroAlloc.Saga.Outbox&version=4.1.4
                    
Install as a Cake Tool

ZeroAlloc.Saga

NuGet Build License: MIT AOT GitHub Sponsors

Source-generated sagas for .NET: declare steps and their compensation on a partial class, and get a state machine with no reflection that runs under NativeAOT, on in-memory, EF Core, ZeroAlloc.ORM or Redis storage.

Why ZeroAlloc.Saga

  • A workflow is one partial class. Steps, correlation keys and compensation are attributes. The generator emits the state machine, the event handlers and the command dispatch, and compensation runs in reverse automatically when a failure event arrives.
  • Works under NativeAOT and trimming. CI publishes and runs AOT samples for the core, the outbox bridge, the ORM store and the resilience bridge. The EF Core store runs on JIT hosts only, blocked upstream.
  • Compile-time diagnostics. 16 generator diagnostics catch authoring mistakes such as gaps in step order, missing correlation keys or a misshaped compensation method, three of them with code fixes.
  • No double dispatch under retry. With the outbox bridge on the EF Core, ORM or Redis store, each step command is committed atomically with the saga state, so an optimistic-concurrency retry cannot send a command twice.

Install

dotnet add package ZeroAlloc.Saga
dotnet add package ZeroAlloc.Mediator.Generator

ZeroAlloc.Saga carries its own generator. Saga commands and events flow through ZeroAlloc.Mediator, whose generator you reference directly.

Quick start

using Microsoft.Extensions.DependencyInjection;
using ZeroAlloc.Mediator;
using ZeroAlloc.Saga;

var services = new ServiceCollection().AddLogging().AddTransient<Warehouse>().AddTransient<Payments>();
services.AddSaga().WithCheckoutSaga(); // generated for the [Saga] class
await using var provider = services.BuildServiceProvider();
var mediator = provider.GetRequiredService<IMediator>();

await mediator.Publish(new OrderPlaced(42, 9.95m), default); // step 1 sends ReserveStock
await mediator.Publish(new StockReserved(42), default);      // step 2 sends ChargeCard
await mediator.Publish(new PaymentDeclined(42), default);    // compensation sends ReleaseStock

public sealed record OrderPlaced(int OrderId, decimal Total) : INotification;
public sealed record StockReserved(int OrderId) : INotification;
public sealed record PaymentCharged(int OrderId) : INotification;
public sealed record PaymentDeclined(int OrderId) : INotification;

public readonly record struct ReserveStock(int OrderId) : IRequest;
public readonly record struct ReleaseStock(int OrderId) : IRequest;
public readonly record struct ChargeCard(int OrderId, decimal Total) : IRequest;
public readonly record struct ShipOrder(int OrderId) : IRequest;

[Saga]
public partial class CheckoutSaga
{
    public int OrderId { get; private set; }
    public decimal Total { get; private set; }

    [CorrelationKey] public int Key(OrderPlaced e) => e.OrderId;
    [CorrelationKey] public int Key(StockReserved e) => e.OrderId;
    [CorrelationKey] public int Key(PaymentCharged e) => e.OrderId;
    [CorrelationKey] public int Key(PaymentDeclined e) => e.OrderId;

    [Step(Order = 1, Compensate = nameof(Release))]
    public ReserveStock Reserve(OrderPlaced e) { (OrderId, Total) = (e.OrderId, e.Total); return new(OrderId); }

    [Step(Order = 2, CompensateOn = typeof(PaymentDeclined))]
    public ChargeCard Charge(StockReserved e) => new(OrderId, Total);

    [Step(Order = 3)]
    public ShipOrder Ship(PaymentCharged e) => new(OrderId);

    public ReleaseStock Release() => new(OrderId);
}

public sealed class Warehouse : IRequestHandler<ReserveStock, Unit>, IRequestHandler<ReleaseStock, Unit>, IRequestHandler<ShipOrder, Unit>
{
    public ValueTask<Unit> Handle(ReserveStock c, CancellationToken ct) => Log($"Reserved stock for {c.OrderId}");
    public ValueTask<Unit> Handle(ReleaseStock c, CancellationToken ct) => Log($"Released stock for {c.OrderId}");
    public ValueTask<Unit> Handle(ShipOrder c, CancellationToken ct) => Log($"Shipped {c.OrderId}");
    internal static ValueTask<Unit> Log(string what) { Console.WriteLine(what); return new(Unit.Value); }
}

public sealed class Payments : IRequestHandler<ChargeCard, Unit>
{
    public ValueTask<Unit> Handle(ChargeCard c, CancellationToken ct) => Warehouse.Log($"Charging {c.Total}");
}

It prints Reserved stock for 42, Charging 9.95 and Released stock for 42. A saga that reaches a terminal state is removed from the store automatically. Concepts explains the lifecycle and what the generator emits.

What you get

  • Correlation by any key type, including strongly typed IDs and composite keys.
  • Automatic compensation on a failure event, and manual compensation through ISagaManager.CompensateAsync.
  • Optimistic concurrency with retry on conflict, each attempt in a fresh DI scope.
  • In-memory storage by default; switch to a durable store with one fluent call.

Optional packages:

Performance

Saga state is persisted through generated Snapshot() and Restore() methods. Exact budgets, asserted by SagaStateAllocationTests:

Call Allocation
SagaStateWriter, every supported shape 0 B
Generated Restore() 0 B
Generated Snapshot(), a 21-byte state 48 B, the returned array

There is no published head-to-head against another saga library yet. Details: docs/performance.md.

Learn more

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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 (2)

Showing the top 2 NuGet packages that depend on ZeroAlloc.Saga.Outbox:

Package Downloads
ZeroAlloc.Saga.Outbox.Redis

Redis-native outbox storage + transactional unit-of-work for ZeroAlloc.Saga.Outbox + ZeroAlloc.Saga.Redis. Closes the cross-backend atomic-dispatch story: saga state save and outbox row commit together (or roll back together) inside a single Redis MULTI/EXEC. Native AOT compatible.

ZeroAlloc.Saga.Outbox.Orm

Transactional outbox unit of work for ZeroAlloc.Saga.Outbox + ZeroAlloc.Saga.Orm. A saga step's outbox rows commit in the same database transaction as the saga state, through ZeroAlloc.Outbox.Orm, or roll back with it. Native AOT compatible.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.1.6 83 10/16/2026
4.1.5 62 10/16/2026
4.1.4 60 10/16/2026
4.1.3 83 10/15/2026
4.1.2 175 10/11/2026
4.1.1 109 10/11/2026
4.1.0 169 9/30/2026
4.0.2 176 9/30/2026
4.0.1 115 9/28/2026
4.0.0 149 9/27/2026
3.1.1 162 9/25/2026
3.1.0 136 9/22/2026
3.0.0 109 9/20/2026
2.0.4 112 9/20/2026
2.0.3 109 9/20/2026
2.0.2 108 9/19/2026
2.0.1 107 9/19/2026
2.0.0 124 8/7/2026
1.5.0 136 5/4/2026
1.4.0 116 5/4/2026
Loading failed