StartAtTail
A new reactor starts from the first event in the event sequence and catches up on everything that is already there. That is what you want for a reactor that builds something from history. It is not what you want for one that sends an email, calls an external system or starts a job, because it will do that once for every event in history the moment it is deployed.
Mark such a reactor with StartAtTail. The first time Chronicle sees it, the reactor begins at the tail of the event sequence and observes only what is appended from then on.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record WelcomeEmailRequested(string Email);
[StartAtTail]public class SendWelcomeEmails : IReactor{ public Task Requested(WelcomeEmailRequested @event) { // Runs for events appended after this reactor was first registered. // It does not run for the history that was already in the event log. return Task.CompletedTask; }}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.StartAtTail
@EventTypedata class WelcomeEmailRequested(val email: String)
@Reactor@StartAtTailclass SendWelcomeEmails { fun requested(event: WelcomeEmailRequested) { // Runs for events appended after this reactor was first registered. // It does not run for the history that was already in the event log. }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.StartAtTail;
@EventTyperecord WelcomeEmailRequested(String email) {}
@Reactor@StartAtTailclass SendWelcomeEmails { void requested(WelcomeEmailRequested event) { // Runs for events appended after this reactor was first registered. // It does not run for the history that was already in the event log. }}defmodule WelcomeEmailRequested do use Chronicle.Events.EventType, id: "welcome-email-requested"
defstruct [:email]end
defmodule SendWelcomeEmails do # Begin at the tail of the event sequence the first time this reactor is # registered, so it never acts on history that predates it. use Chronicle.Reactors.Reactor, start_at_tail: true
@handles WelcomeEmailRequested
@impl true def handle(%WelcomeEmailRequested{}, _context) do # Send the email. :ok endendimport { eventType, reactor, startAtTail } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()class WelcomeEmailRequested { @field(String) readonly email: string;
constructor(email: string = '') { this.email = email; }}
@reactor()@startAtTail()class SendWelcomeEmails { welcomeEmailRequested(event: WelcomeEmailRequested): void { // Runs for events appended after this reactor was first registered. // It does not run for the history that was already in the event log. }}What it does
Section titled “What it does”StartAtTail decides where a reactor starts, once. It does not filter events and it does not change what the reactor does afterwards.
- First registration only. A reactor that Chronicle already holds a position for keeps that position. The marker is safe to leave in place; it never skips events after a restart or a redeploy.
- Per namespace. Each namespace has its own observer, so a reactor that appears in a new namespace starts at the tail of that namespace.
- Replay is unaffected. An explicit replay still replays from the beginning. See Replay and OnceOnly for keeping side effects out of a replay.
Compared with OnceOnly
Section titled “Compared with OnceOnly”OnceOnly and StartAtTail cover different moments, and a reactor with side effects often needs both:
| Moment it acts on | What it does | |
|---|---|---|
OnceOnly | A replay of an observer that already exists | Skips the handler for replayed events |
StartAtTail | The first registration of a new observer | Starts at the tail, so history is never delivered |
Neither is an exactly-once guarantee. Recovering a failed partition re-delivers an event as ordinary observation. See Delivery identity.
Renaming a reactor
Section titled “Renaming a reactor”A reactor’s default id is its full type name, so renaming it or moving its namespace makes it a new reactor, and a reactor marked StartAtTail starts at the tail again. That is usually the safe outcome. It also means events appended between the old reactor’s last position and the rename are not seen by the new one. If the new reactor must continue from where the old one stopped, give it its old id explicitly. See Reactor identity and renaming.
Server and client versions
Section titled “Server and client versions”Both the Chronicle server and the client must support StartAtTail. A server that predates it ignores the request and the reactor catches up from the beginning, as before. Upgrade the server first, then the clients.