Dispatch station for Trax trains. Hand it the cargo and it routes it to the right train, no need to know which train handles what.
Trax is a layered framework split across several repos. You can stop at whatever layer solves your problem. You are here: Trax.Mediator.
| Repo | Adds |
|---|---|
| Trax.Core | Pipelines, junctions, railway error propagation |
| Trax.Effect | Execution logging, DI, pluggable storage |
| Trax.Mediator | Decoupled dispatch via TrainBus |
| Trax.Scheduler | Cron schedules, retries, dead-letter queues |
| Trax.Api | GraphQL API for remote access |
| Trax.Dashboard | Blazor monitoring UI |
| Trax.Cli | trax-cli project scaffolding tool |
| Trax.Samples | Sample apps and a dotnet new template |
Full documentation: traxsharp.net/docs.
When one part of your system needs to send a train, it has to know exactly which train class to use. Controllers depend on concrete train types, stops that trigger other trains need direct references, and everything gets coupled together.
TrainBus is a dispatch station. At startup it builds a map of what cargo goes on which train. To send something, you just drop off the cargo:
public class OrderController(ITrainBus trainBus) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Create(OrderRequest request)
{
var receipt = await trainBus.RunAsync<OrderReceipt>(request);
return Ok(receipt);
}
}No reference to ProcessOrderTrain. The dispatch station looks at the cargo type and sends it on the right train.
dotnet add package Trax.MediatorTrax.Mediator depends on Trax.Effect, which depends on Trax.Core. Both are pulled in transitively.
Register your train assemblies during startup. The dispatch station scans them for all IServiceTrain<TIn, TOut> implementations and builds the cargo-to-train map automatically:
builder.Services.AddTrax(trax =>
trax.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(typeof(Program).Assembly)
);Multiple assemblies:
builder.Services.AddTrax(trax =>
trax.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(
typeof(Program).Assembly,
typeof(SomeTrainInAnotherProject).Assembly
)
);That's it. Every IServiceTrain<TIn, TOut> in those assemblies is now dispatchable through ITrainBus.
// With a return value: send cargo, get a delivery back
var user = await trainBus.RunAsync<User>(new CreateUserRequest
{
Email = "[email protected]",
Name = "Jane"
});
// One-way: send cargo, no delivery expected
await trainBus.RunAsync(new SendNotificationRequest { UserId = userId });var receipt = await trainBus.RunAsync<OrderReceipt>(request, cancellationToken);The cancellation signal is forwarded to the train and all its stops.
A stop can dispatch another train mid-journey. Pass the current Metadata to establish a parent-child relationship between the journeys:
public class SendWelcomeEmailJunction(ITrainBus trainBus) : Junction<User, Unit>
{
public override async Task<Unit> Run(User input)
{
await trainBus.RunAsync(new SendEmailRequest
{
To = input.Email,
Template = "welcome"
});
return Unit.Default;
}
}The dispatch station exposes a registry of all known trains, which is useful for tooling and dashboards:
public class TrainListEndpoint(ITrainRegistry registry)
{
public IEnumerable<string> GetTrainNames()
=> registry.InputTypeToTrain.Values.Select(t => t.Name);
}At startup, AddMediator scans the provided assemblies for types implementing IServiceTrain<TIn, TOut>. It builds a dictionary from cargo type (input) to train type and registers each train in the DI container. When you call RunAsync<TOut>(input), the dispatch station looks up input.GetType(), resolves the matching train from DI, and sends it on its way.
When you need recurring background jobs with retries and dead-lettering, move up to Trax.Scheduler.
MIT
Trax is an open-source .NET framework provided by TraxSharp. This project is an independent community effort and is not affiliated with, sponsored by, or endorsed by the Utah Transit Authority, Trax Retail, or any other entity using the "Trax" name in other industries.