Replay
A reactor sees the same event twice for different reasons: once as it happens, and again whenever its observer is replayed. Those often call for different work — a confirmation that should go out once when an order is placed has no business going out again while a read model is being rebuilt.
Mark a second handler for the same event type with the Replay attribute and it takes over for the duration
of the replay.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record ReplayAwareOrderPlaced(string OrderId);
public class ReplayAwareOrderReactor : IReactor{ public void SendConfirmation(ReplayAwareOrderPlaced @event) { // Runs as the event happens. }
[Replay] public void RebuildProjectionCache(ReplayAwareOrderPlaced @event) { // Runs instead of SendConfirmation while the observer is replaying. }}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Replay
@EventType(id = "replay-aware-order-placed")data class ReplayAwareOrderPlaced(val orderId: String)
@Reactorclass ReplayAwareOrderReactor { fun sendConfirmation(event: ReplayAwareOrderPlaced) { // Runs as the event happens. }
@Replay fun rebuildProjectionCache(event: ReplayAwareOrderPlaced) { // Runs instead of sendConfirmation while the observer is replaying. }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Replay;
@EventType(id = "replay-aware-order-placed")record ReplayAwareOrderPlaced(String orderId) {}
@Reactorclass ReplayAwareOrderReactor { void sendConfirmation(ReplayAwareOrderPlaced event) { // Runs as the event happens. }
@Replay void rebuildProjectionCache(ReplayAwareOrderPlaced event) { // Runs instead of sendConfirmation while the observer is replaying. }}import { EventContext, eventType, ICanBeNotifiedWhenReplay, reactor } from '@cratis/chronicle';
@eventType()class ReplayAwareOrderPlaced { constructor(readonly orderId: string = '') {}}
// Implement ICanBeNotifiedWhenReplay to be told when a full replay of this reactor's// observation begins and ends - useful for suppressing side effects (e.g.// notifications) while historical events are being reprocessed. A throwing hook marks// the batch Failed, the same as a handler that throws.@reactor()class ReplayAwareOrderReactor implements ICanBeNotifiedWhenReplay { private _isReplaying = false;
async beginReplay(): Promise<void> { this._isReplaying = true; }
async endReplay(): Promise<void> { this._isReplaying = false; }
async replayAwareOrderPlaced(event: ReplayAwareOrderPlaced, context: EventContext): Promise<void> { if (this._isReplaying) { // Runs during replay too - skip side effects that must not repeat. return; }
// Runs as the event happens for the first time. }}The rules are:
- With a
Replayhandler, only it runs during a replay — the regular handler does not also run. - Without one, the regular handler runs during a replay exactly as it always has, so adding this to one event type changes nothing for the others.
- An event type handled only by a
Replayhandler is still subscribed to, so the replay it exists for delivers it.
Reach for OnceOnly instead when the side effect should simply not happen again. Use Replay when
a replay needs to do something different rather than nothing. Neither covers the re-delivery that follows a
failed partition being recovered — for that, see Delivery identity.