Skip to content

CHR0051: Reducer current read model parameter must be nullable

Chronicle passes null as the current read model for the event that brings an instance into existence, so a reducer method cannot promise the parameter is never null. Declare the parameter as nullable and handle the null case as the creation of the instance.

Warning

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reducers;
public record Chr0051ViolationBookStatus(string Title, bool IsAvailable);
[EventType("book-added")]
public record Chr0051ViolationBookAdded(string Title);
public class Chr0051ViolationBookStatusReducer : IReducerFor<Chr0051ViolationBookStatus>
{
// Warning CHR0051: Reducer method 'Reduce' declares its current read model parameter
// as 'Chr0051ViolationBookStatus' rather than 'Chr0051ViolationBookStatus?'
public Chr0051ViolationBookStatus Reduce(Chr0051ViolationBookAdded @event, Chr0051ViolationBookStatus current, EventContext context) =>
new(@event.Title, true);
}
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reducers;
public record Chr0051FixBookStatus(string Title, bool IsAvailable);
[EventType("book-added")]
public record Chr0051FixBookAdded(string Title);
public class Chr0051FixBookStatusReducer : IReducerFor<Chr0051FixBookStatus>
{
// The current read model parameter is nullable, handling creation correctly.
public Chr0051FixBookStatus Reduce(Chr0051FixBookAdded @event, Chr0051FixBookStatus? current, EventContext context) =>
current ?? new(@event.Title, true);
}

A reducer’s job is to fold an event and the current read model state into the next state. When an event is the first for a given event source, there is no current state — Chronicle passes null for the current parameter. A reducer method that declares current as non-nullable makes a promise it cannot keep, because the very first event for an instance will pass null.

In a #nullable disable context, the annotation carries no claim at all, so Chronicle keeps dispatching to those methods without complaint. This rule fires only when a method makes an explicit non-nullable annotation — the broken promise.

Declare current as nullable and handle the null case — typically by creating the initial instance — to make the reducer correct for both the first event and every subsequent one.

  • CHR0006: Reducer method signature must match allowed signatures — validates the overall shape of a reducer method.
  • CHR0036: Reducer must not have mutable state — ensures reducers remain stateless and replay-safe.