Train
The base class of every train. A train takes an input, runs the junctions its Junctions() override declares, and produces a result or an exception. It has no persistence, no lifecycle hooks and no container: those come with ServiceTrain, which derives from it and is what most applications use.
Derive from Train directly only when you use Trax.Core on its own, without Trax.Effect.
Signature
namespace Trax.Core.Train;
public abstract class Train<TInput, TReturn> : IRoute<TInput, TReturn>
{
protected Train();
public string ExternalId { get; set; }
[JsonIgnore]
public CancellationToken CancellationToken { get; protected set; }
public bool IsDeclaringChain { get; }
public virtual Task<TReturn> Run(TInput input, CancellationToken cancellationToken = default);
public Task<Either<Exception, TReturn>> RunEither(TInput input);
public ChainRecorder DeclaredChain();
protected virtual Task<Either<Exception, TReturn>> Junctions();
// Chain methods, called inside Junctions()
protected MonadTask<TInput, TReturn> Chain<TJunction>() where TJunction : class;
protected MonadTask<TInput, TReturn> IChain<TJunction>() where TJunction : class;
protected MonadTask<TInput, TReturn> ShortCircuit<TJunction>() where TJunction : class;
protected Monad<TInput, TReturn> Extract<TIn, TOut>();
protected Monad<TInput, TReturn> AddServices<T1>(T1 service); // up to seven services
protected Either<Exception, TReturn> Resolve();
// ...plus the instance and explicit-type overloads listed on each method's page
}IRoute<TInput, TReturn> is the one-method contract (Run(input, cancellationToken)) that trains implement; see IRoute.
Type Parameters
| Type Parameter | Description |
|---|---|
TInput | The train's input. It is the first value in Memory, so the first junction can take it. |
TReturn | The train's result. Resolve() takes it out of Memory at the end of the chain. |
Members
| Member | Description |
|---|---|
ExternalId | Identifies one run across logs, stored metadata and failure data (TrainExceptionData.TrainExternalId). A new GUID in 32-digit "N" format when the train is constructed; set it before Run to correlate the run with an id you already have. |
CancellationToken | The token of the current run, set by Run and copied to every junction before it runs. Not serialized. A caller cannot set it; pass the token to Run. |
IsDeclaringChain | True while DeclaredChain is reading the chain rather than running it. |
| Run / RunEither | Runs the train. Run throws the failure, RunEither returns it as Left. Run is virtual here and sealed on ServiceTrain. |
| DeclaredChain | Reads the declared chain without resolving or running a junction. The startup chain check calls it. |
| Junctions | The override that declares the chain. The base implementation throws NotImplementedException. |
Chain, IChain, ShortCircuit, Extract, AddServices, Resolve | The chain methods. All are protected: they exist to be called inside Junctions(). |
NewMonad() is also protected virtual, but it is the seam ServiceTrain uses to supply its container and is hidden from completion. It is not an extension point.
Example
using LanguageExt;
using Trax.Core.Train;
public class ScoreTrain : Train<ScoreInput, ScoreResult>
{
protected override Task<Either<Exception, ScoreResult>> Junctions() =>
Chain<NormalizeScore>()
.Chain<RankScore>()
.Resolve();
}
var result = await new ScoreTrain().Run(new ScoreInput(42));The train constructs each junction itself, filling its constructor's parameters from Memory: the input, the outputs of earlier junctions, and services added with AddServices. A plain Train has no container, so a parameter Memory cannot supply fails the chain. A ServiceTrain also falls back to its container.
Remarks
- Overriding
Run.Runstays virtual so code built on Trax.Core alone can wrap it. An override that does not callbase.Runruns something other than the declared chain, soDeclaredChain(), and any check built on it, no longer describes what runs (Trax.Core ADR 0003). AServiceTraincannot do this. - One run at a time. A train instance carries the state of the run in progress. Do not run the same instance from two callers at once.
- Nothing escapes
RunEither. A junction's exception, including cancellation, comes back asLeft.
See Trains and Junctions for the concepts.
Package
dotnet add package Trax.Core