How-to guide

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.

SDK Reference

UseBroadcaster | AddLifecycleHook