Skip to content

CHR0022: Reactor methods returning event side effects must be marked with [OnceOnly]

A reactor handler method that returns event(s) as a side effect causes Chronicle to append those events to the event log. Unless the method is marked with [OnceOnly], it runs again during replay operations — appending duplicate events.

Mark such a method with [OnceOnly].

Warning

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;
[EventType]
public record Chr0022OrderPlaced(Guid OrderId, decimal Total);
[EventType]
public record Chr0022InvoiceRaised(Guid OrderId, decimal Total);
public class Chr0022Invoicing : IReactor
{
// Warning CHR0022: this method returns a side-effect event, so Chronicle appends Chr0022InvoiceRaised
// whenever Chr0022OrderPlaced is observed — including during replay, which would append it again.
public Chr0022InvoiceRaised OrderPlaced(Chr0022OrderPlaced @event) =>
new(@event.OrderId, @event.Total);
}
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;
[EventType]
public record Chr0022OrderPlacedFixed(Guid OrderId, decimal Total);
[EventType]
public record Chr0022InvoiceRaisedFixed(Guid OrderId, decimal Total);
public class Chr0022InvoicingFixed : IReactor
{
// [OnceOnly] makes Chronicle run the handler a single time per event source and skip it during
// replays, so the side-effect event is appended exactly once.
[OnceOnly]
public Chr0022InvoiceRaisedFixed OrderPlaced(Chr0022OrderPlacedFixed @event) =>
new(@event.OrderId, @event.Total);
}

A reactor observes events and produces side effects. When a handler returns an event, Chronicle appends it to the event log as a follow-up fact. Reactors are also re-run during replay operations — observer rewind, redaction, and revision — so a handler that appends an event and is not idempotent would append that event a second time on every replay, producing duplicate facts and, in turn, incorrect projected state.

[OnceOnly] tells Chronicle to run the handler a single time per event source and to skip it during replays, so the side-effect event is appended exactly once. Side effects that are naturally idempotent, or that do not append events, don’t need it — which is why the rule targets specifically the methods that return events.

  • Reactors — reactor side effects and idempotency.