React to an event
Goal: when an event-sourced slice records something — a book is added, an author is registered — you want something else to happen automatically: a notification goes out, another system is told, a follow-up command runs. That’s a reactor.
This is part of Arc’s Chronicle integration. Direct database-backed Arc slices use commands, queries, and ordinary services; reactors become available when the write side records events in Chronicle.
A reactor does, a projection shows
Section titled “A reactor does, a projection shows”A projection builds queryable state; a reactor acts. Where you’d reach for a projection to display data, reach for a reactor to cause an effect. IReactor is a marker interface — there’s nothing to override. Chronicle discovers supported handler signatures by the type of their first parameter. Register the integration and event types. For a returned-command reaction, use the supported Task<TCommand> signature shown in command side effects.
-
For a side effect, call a collaborator. Inject whatever does the work and handle the event:
public class NewArrivalsAnnouncer(INewArrivalsFeed feed) : IReactor{[OnceOnly]public async Task BookAdded(BookAddedToCatalog @event, EventContext context) =>await feed.Announce($"New on the shelf: {@event.Title}");} -
To invoke another behavior, execute a command. Prefer a returned command, or inject
ICommandPipelinewhen you need to inspect the result. Chronicle also supports returned events directly; returning them preserves the side-effect pipeline without a manual default-log append:public class CatalogIndexer(ICommandPipeline commands) : IReactor{[OnceOnly]public async Task BookAdded(BookAddedToCatalog @event, EventContext context){BookId bookId = Guid.Parse(context.EventSourceId.Value);var result = await commands.Execute(new CreateSearchIndex(bookId, @event.Title));if (!result.IsSuccess){throw new SearchIndexingFailed();}}}
These reactor fragments reuse BookId, BookTitle, BookAddedToCatalog, CreateSearchIndex, and the named SearchIndexingFailed exception from command side effects. Place them in the same Catalog.Books namespace and supply INewArrivalsFeed for the first example. Import System, System.Threading.Tasks, Cratis.Arc.Commands, Cratis.Chronicle.Events, and Cratis.Chronicle.Reactors as needed.
Convert the context’s framework identity to BookId at the boundary; the command and event retain their domain types. Throwing on an unsuccessful command fails the reactor so Chronicle’s failure/recovery policy applies. Logging and returning normally would acknowledge the event despite the failed indexing. A manual command has no inherited HTTP actor; configure authorization deliberately. Returned-command execution explains system roles.
Direct appends to another event sequence or another store remain advanced options when a returned side effect cannot express the target. ARCCHR0003 documents that boundary; it is analyzer guidance, not a runtime ban on all event-log access.
See also
Section titled “See also”- Returning commands as side effects — execute commands as side effects from a reactor.
- Add event sourcing to an Arc slice — where reactors enter the Arc model.
- Return a result or an error — what the command you execute can return.