Skip to content

Getting Started with Reactors

Reactors observe events and run side effects. Use them for work such as sending notifications, calling an external API, publishing integration messages, or triggering a workflow in another system.

Reactors do not build queryable state. Use a projection or reducer when the outcome should be a read model.

A reactor handles one or more event types. The event is still a normal Chronicle event: an immutable fact with a stable event type identity.

[EventType]
public record ReactorOrderPlaced(string CustomerEmail, decimal TotalAmount);

The client SDK decides how a reactor is declared. The common shape is the same: mark a type or module as a reactor, add a handler for the event, and accept event context when the side effect needs metadata such as sequence number, occurrence time, event store, namespace, or correlation.

public interface ReactorEmailGateway
{
Task SendOrderPlaced(string email, decimal amount, DateTimeOffset occurred);
}
public class OrderNotificationsReactor(ReactorEmailGateway emailGateway) : IReactor
{
public Task Placed(ReactorOrderPlaced @event, EventContext context) =>
emailGateway.SendOrderPlaced(
@event.CustomerEmail,
@event.TotalAmount,
context.Occurred);
}

Registration is client-specific. Some clients discover reactor types and register them as part of event-store startup. Others register a reactor instance or include the reactor module in client startup options.

public class ReactorRegistration
{
public Task Register(IEventStore eventStore) => eventStore.Reactors.Register();
}

By default, clients derive a reactor identifier from the type or module name. Set an explicit ID when the reactor’s identity must survive a rename. Changing the ID makes Chronicle treat it as a different observer, with a different observation position.

[Reactor(id: "order-notifications")]
public class NamedOrderNotificationsReactor : IReactor
{
public Task Placed(ReactorOrderPlaced @event) => Task.CompletedTask;
}

Reactors may see the same event again after replay, recovery, reconnect, or a retry. Make every side effect idempotent. Common patterns are idempotency keys, check-then-act storage, database upserts, or downstream APIs that tolerate duplicate requests.