﻿---
title: MassTransit sagas and consumers
description: Keep a temporal monitor per correlation id in a consumer, and check a saga's state changes against its state machine.
url: https://docs-v3-preview.elastic.dev/integration/masstransit
---

# MassTransit sagas and consumers
No package: this is a recipe. This repository does not reference MassTransit, so the snippet is **not compiled here**: treat it as the shape, and check the signatures against the
version you use.
A saga is a state machine plus a stream of messages, which is what the temporal monitor is for. Keep one `TemporalMonitor` per correlation id and feed it the messages:
```csharp
public sealed class OrderConsumer(MonitorStore monitors, ConstraintDiagnostics diagnostics) : IConsumer<OrderShipped>
{
    public Task Consume(ConsumeContext<OrderShipped> context)
    {
        // TemporalMonitor<OrderEvent> built from OrderRules.All; the callback runs once, when the store creates it
        var monitor = monitors.For(context.Message.OrderId, m => m.ReportTo(diagnostics));
        monitor.Observe(new OrderEvent.Shipped(context.Message.OrderId));
        if (monitor.AnyViolated) /* already logged and counted; fault the message if it must not proceed */;
        return Task.CompletedTask;
    }
}
```

`ReportTo` comes from `Nullean.Entities.Diagnostics` ([Logging and metrics](https://docs-v3-preview.elastic.dev/integration/diagnostics)): every violation becomes a log record and a count, including a deadline
that passes between messages, which no `Consume` call would see.
A deadline ("shipped within 5 minutes of confirmation") fires from the monitor's `TimeProvider`, so a test advances a `ManualTimeProvider` and the missed deadline appears
without sleeping. To check a recorded run instead, `Trace.Check(events, OrderRules.All)` takes the log (a MassTransit audit store, a Marten stream) and returns every violation with its time.
For the saga's own state, declare the allowed transitions once as a `StateMachine<string, string>`, use `machine.AsConstraint<Saga>(s => s.CurrentState)` as a sequential rule,
and call `constraints.Check(before, after)` around the state change.