Skip to content

Returning Side Effects from Reactor Handler Methods

Reactor handler methods can return side-effect events directly instead of taking a dependency on IEventLog. The framework automatically appends the returned events to the correct sequence after the handler completes.

Return a single event directly from a handler method — synchronously or as a Task<T>:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;
public class WarehouseReactor : IReactor
{
// Synchronous — no async overhead when the result is already available
public StockDecreased BookReserved(BookReserved @event, EventContext context) =>
new StockDecreased(@event.Isbn, 1);
// Asynchronous — use when you need to await something before producing the event
public async Task<StockDecreased> BookReservedAsync(BookReserved @event, EventContext context)
{
var available = await FetchCurrentStockAsync(@event.Isbn);
return new StockDecreased(@event.Isbn, available);
}
Task<int> FetchCurrentStockAsync(string isbn) => Task.FromResult(0);
}

The returned event is appended to the event log using the EventSourceId from the incoming EventContext. No IEventLog injection required.

Return IEnumerable<TEvent> to append several events in one handler call:

public IEnumerable<object> BookReserved(BookReserved @event, EventContext context) =>
[
new StockDecreased(@event.Isbn, 1),
new StockLow(@event.Isbn),
];

Set metadata once on the reactor type so every returned event inherits it automatically.

Implement this interface to supply a custom EventSourceId for all side-effect events from this reactor:

public class WarehouseReactor : IReactor, ICanProvideEventSourceId
{
readonly string _warehouseId;
public WarehouseReactor(string warehouseId) => _warehouseId = warehouseId;
public EventSourceId GetEventSourceId() => _warehouseId;
public StockDecreased BookReserved(BookReserved @event, EventContext context) =>
new StockDecreased(@event.Isbn, 1);
}

Implement this interface to attach a Subject (e.g. a user or principal) to appended events:

public class OrderReactor : IReactor, ICanProvideSubject
{
readonly string _userId;
public OrderReactor(string userId) => _userId = userId;
public Subject GetSubject() => new Subject(_userId);
}

Implement this interface to specify a runtime EventStreamId:

public class TenantReactor : IReactor, ICanProvideEventStreamId
{
readonly string _tenantId;
public TenantReactor(string tenantId) => _tenantId = tenantId;
public EventStreamId GetEventStreamId() => _tenantId;
}

[EventStreamType] and [EventSourceType] Attributes

Section titled “[EventStreamType] and [EventSourceType] Attributes”

Apply these attributes to the reactor class for a compile-time stream or source type:

[EventStreamType("warehouse")]
[EventSourceType("product")]
public class WarehouseReactor : IReactor
{
public StockDecreased BookReserved(BookReserved @event, EventContext context) =>
new StockDecreased(@event.Isbn, 1);
}
MetadataPriority
EventSourceIdICanProvideEventSourceIdeventContext.EventSourceId
EventStreamIdICanProvideEventStreamId[EventStreamId] attribute → null
EventStreamType[EventStreamType] attribute → null
EventSourceType[EventSourceType] attribute → null
SubjectICanProvideSubjectnull

Extend the pipeline by registering a custom IReactorSideEffectHandler:

public class MyHandler : IReactorSideEffectHandler
{
public bool CanHandle(ReactorContext reactorContext, object value) =>
value is MySpecialResult;
public async Task Handle(ReactorContext reactorContext, IEventStore eventStore, object value)
{
// process value
}
}
// Register in DI
services.AddSingleton<IReactorSideEffectHandler, MyHandler>();
Return typeHandler invoked
TEventEventResultHandler — appends single event
Task<TEvent>EventResultHandler — appends single event
IEnumerable<TEvent>EventsResultHandler — appends each event
Task<IEnumerable<TEvent>>EventsResultHandler
void / TaskNo side effects appended