Write a headless event sink
Source ITrainEventHandler.cs
A headless sink writes events somewhere other than a client: a database, a file, an external API. This one stores the report a train returns into a repository of your own. What sinks are is on Broadcaster sinks.
1. Handle the events
using System.Text.Json;
using Trax.Effect.Services.TrainEventBroadcaster;
internal sealed class GeocodeDriftPersister(IGeocodeDriftRepository repo) : ITrainEventHandler
{
public async Task HandleAsync(TrainLifecycleEventMessage message, CancellationToken ct)
{
if (message.EventType != "Completed") return;
if (message.TrainName != typeof(ICheckGeocodeDriftTrain).FullName) return;
if (string.IsNullOrEmpty(message.Output)) return;
var report = JsonSerializer.Deserialize<GeocodeDriftReport>(message.Output)!;
await repo.SaveAsync(report, ct);
}
}Filter on EventType: the broadcaster also delivers data-change signals (DataChanged, with no train fields) to every ITrainEventHandler.
2. Register it
using Trax.Effect.Services.TrainEventBroadcaster;
builder.Services.AddSingleton<ITrainEventHandler, GeocodeDriftPersister>();An ITrainEventHandler receives events that arrive over a transport, so the host needs UseBroadcaster with one.
3. Also react to local runs (if the host runs trains)
A handler sees only remote events. If the same host runs the train and must react there too, register the sink as an ITrainLifecycleHook as well (AddLifecycleHook); the SignalR sink is registered both ways for this reason.