Reactors
Reactors are observers that react to events and execute side effects. They are ideal for integrating with external systems, sending notifications, or emitting follow-up actions when events occur.
Key Concepts
Section titled “Key Concepts”- Event-driven side effects - Reactors run when events are appended and can call external services or enqueue work
- Convention-based methods - Public methods with supported signatures are discovered automatically
- Event context access - Optional
EventContextprovides metadata like timestamps and identifiers - Event-source isolation - Events are processed per event source, in order, to keep behavior consistent
When to Use Reactors
Section titled “When to Use Reactors”Reactors are a good fit when you need to:
- Trigger notifications or workflows when specific events occur
- Synchronize with external systems
- Run validations or side effects that do not belong in projections or reducers
- Emit follow-up events based on patterns in the event stream
Basic Example
Section titled “Basic Example”using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record ReactorsIndexEmailConfirmed(string Email);
public class ReactorsIndexEmailNotificationsReactor : IReactor{ public Task Confirmed(ReactorsIndexEmailConfirmed @event, EventContext context) => SendConfirmationAsync(@event.Email, context.Occurred);
Task SendConfirmationAsync(string email, DateTimeOffset occurred) => Task.CompletedTask;}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactor
@EventType(id = "reactors-index-email-confirmed")data class ReactorsIndexEmailConfirmed(val email: String)
@Reactorclass ReactorsIndexEmailNotificationsReactor { fun confirmed(event: ReactorsIndexEmailConfirmed, context: EventContext) { sendConfirmation(event.email, context.occurred) }
private fun sendConfirmation(email: String, occurred: java.time.Instant) {}}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import java.time.Instant;
@EventType(id = "reactors-index-email-confirmed")record ReactorsIndexEmailConfirmed(String email) {}
@Reactorclass ReactorsIndexEmailNotificationsReactor { void confirmed(ReactorsIndexEmailConfirmed event, EventContext context) { sendConfirmation(event.email(), context.getOccurred()); }
private void sendConfirmation(String email, Instant occurred) {}}defmodule MyApp.Events.ReactorsIndexEmailConfirmed do use Chronicle.Events.EventType, id: "reactors-index-email-confirmed"
defstruct [:email]end
defmodule MyApp.Reactors.ReactorsIndexEmailNotificationsReactor do use Chronicle.Reactors.Reactor
alias MyApp.Events.ReactorsIndexEmailConfirmed
@handles ReactorsIndexEmailConfirmed
@impl true def handle(%ReactorsIndexEmailConfirmed{} = event, context) do send_confirmation(event.email, Map.get(context, :occurred))
:ok end
defp send_confirmation(_email, _occurred), do: :okendimport { eventType, reactor, EventContext } from '@cratis/chronicle';
@eventType()class ReactorsIndexEmailConfirmed { constructor(readonly email: string) {}}
@reactor()class ReactorsIndexEmailNotificationsReactor { // Method name must be the exact camelCase of the event's class name - // Chronicle discovers handlers by name, not by parameter type. async reactorsIndexEmailConfirmed(event: ReactorsIndexEmailConfirmed, context: EventContext): Promise<void> { await this.sendConfirmation(event.email, context.occurred); }
private async sendConfirmation(email: string, occurred: Date): Promise<void> {}}