Add your own trains to a template
Source content/
The steps below assume a project made from one of the
project templates. A query train needs trax-hub or trax-api
(they serve GraphQL); a scheduled train needs trax-hub or trax-scheduler (they run the
scheduler).
Put each train in its own folder under Trains/, with its interface, input, output and a
Junctions/ folder, laid out as HelloWorld/ is. AddMediator(typeof(Program).Assembly) in
Program.cs finds it; nothing else is registered.
Add a query train
-
Add the train and its interface.
[TraxQuery]serves it underdiscover, and every train the API exposes carries[TraxAuthorize]or[TraxAllowAnonymous]; the template's demo key holds theUserrole.using Trax.Core.Functional; using Trax.Effect.Attributes; using Trax.Effect.Services.ServiceTrain; namespace MyCompany.Hub.Trains.GetCustomer; public interface IGetCustomerTrain : IServiceTrain<GetCustomerInput, CustomerOutput>; [TraxAuthorize(Roles = "User")] [TraxQuery(Description = "Fetches a customer by ID")] public class GetCustomerTrain : ServiceTrain<GetCustomerInput, CustomerOutput>, IGetCustomerTrain { protected override Task<Either<Exception, CustomerOutput>> Junctions() => Chain<FetchCustomerJunction>().Resolve(); } -
Add its input, output and junction. Derive
EffectJunction<TIn, TOut>, as the template's junctions do, soAddJunctionProgress()reports it.using Trax.Effect.Services.EffectJunction; namespace MyCompany.Hub.Trains.GetCustomer; public record GetCustomerInput { public required string Id { get; init; } } public record CustomerOutput { public required string Id { get; init; } public required string Name { get; init; } } public class FetchCustomerJunction : EffectJunction<GetCustomerInput, CustomerOutput> { public override Task<CustomerOutput> Run(GetCustomerInput input) => Task.FromResult(new CustomerOutput { Id = input.Id, Name = $"Customer {input.Id}" }); } -
Run the host and call the field. It is
getCustomerunderquery { discover { ... } }: the interface name without theIand theTrain, in camelCase.curl -X POST http://localhost:5400/trax/graphql \ -H 'Content-Type: application/json' \ -H 'X-Api-Key: demo-key-do-not-use-in-production' \ -d '{"query":"{ discover { getCustomer(input: { id: \"42\" }) { id name } } }"}'
The host refuses to start if an exposed train has neither [TraxAuthorize] nor
[TraxAllowAnonymous], and if a junction needs an input that nothing before it produces.
Add a scheduled train
-
Add the train. A scheduled train's input implements
IManifestProperties, so the scheduler can store it on the manifest.using Trax.Core.Functional; using Trax.Effect.Models.Manifest; using Trax.Effect.Services.ServiceTrain; namespace MyCompany.Scheduler.Trains.SyncCustomers; public record SyncCustomersInput : IManifestProperties { public string Region { get; init; } = "us-east"; } public interface ISyncCustomersTrain : IServiceTrain<SyncCustomersInput, Unit>; public class SyncCustomersTrain : ServiceTrain<SyncCustomersInput, Unit>, ISyncCustomersTrain { protected override Task<Either<Exception, Unit>> Junctions() => Chain<FetchCustomersJunction>() .Chain<UpsertCustomersJunction>() .Resolve(); } -
Add
FetchCustomersJunctionandUpsertCustomersJunctionunder the train'sJunctions/folder, as in step 2 above: the first takes theSyncCustomersInput, the last returnsUnit. -
In
Program.cs, add the schedule after thehello-worldone, insideAddScheduler:scheduler .Schedule<ISyncCustomersTrain>( "sync-customers", new SyncCustomersInput { Region = "us-east" }, Every.Hours(1) );
The first argument is the manifest's id. Schedule updates the manifest with that id at every
startup, so renaming it creates a new manifest and leaves the old one in place. The dashboard
shows the new manifest and each run.
See Scheduling for cron schedules, dependent trains, bulk scheduling and dead letters, and Trains & Junctions for what a junction can do.
SDK Reference
AddMediator | TraxQuery / TraxMutation | TraxAuthorize | Schedule | EffectJunction | ServiceTrain