Implement a broadcast transport
UseBroadcaster hands its events to whatever ITrainEventBroadcaster is registered and reads incoming ones from
the registered ITrainEventReceiver. A transport for another bus is those two classes and an extension method that
registers them.
1. Publish
PublishAsync is called on the run's path, from a lifecycle hook the train awaits. Queue the message and return;
send from a background loop.
using System.Threading.Channels;
using Trax.Effect.Services.TrainEventBroadcaster;
public sealed class MyBusBroadcaster(string connectionString) : ITrainEventBroadcaster
{
private readonly Channel<TrainLifecycleEventMessage> _queue =
Channel.CreateBounded<TrainLifecycleEventMessage>(
new BoundedChannelOptions(1024) { FullMode = BoundedChannelFullMode.DropOldest });
public string ConnectionString { get; } = connectionString;
public Task PublishAsync(TrainLifecycleEventMessage message, CancellationToken ct)
{
_queue.Writer.TryWrite(message);
return Task.CompletedTask;
}
}On a host that calls AddJunctionEvents(), the broadcaster also receives every junction event: a message whose
Junction is set (TrainLifecycleEventMessage.IsJunctionEvent(message.EventType)). Route those apart from train
events, to a topic of their own, so a receiver that predates junction events never sees one.
2. Receive
TrainEventReceiverService calls StartAsync with the handler that dispatches to the host's ITrainEventHandlers,
and calls it again after a failed start, with a growing delay, so a second call must release what the first opened.
using Trax.Effect.Services.TrainEventBroadcaster;
public sealed class MyBusReceiver(string connectionString) : ITrainEventReceiver
{
public string ConnectionString { get; } = connectionString;
public Task StartAsync(
Func<TrainLifecycleEventMessage, CancellationToken, Task> handler,
CancellationToken ct) =>
Task.CompletedTask; // subscribe, and call handler for each deserialized message
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}Messages carry the publishing host's InstanceId; the receiver service drops a host's own, so a transport delivers
everything it receives.
3. Register both
using Microsoft.Extensions.DependencyInjection;
using Trax.Effect.Configuration.BroadcasterBuilder;
using Trax.Effect.Services.TrainEventBroadcaster;
public static class MyBusBroadcasterExtensions
{
public static BroadcasterBuilder UseMyBus(this BroadcasterBuilder builder, string connectionString)
{
builder.ServiceCollection.AddSingleton<ITrainEventBroadcaster>(new MyBusBroadcaster(connectionString));
builder.ServiceCollection.AddSingleton<ITrainEventReceiver>(new MyBusReceiver(connectionString));
return builder;
}
}The host then calls effects.UseBroadcaster(b => b.UseMyBus(connectionString)).
See also
- How event broadcasting works
- UseRabbitMq, the shipped transport, for the delivery rules it follows