Trax.Effect
Trax.Effect gives every train run a record: a row with its state, start and end time, the junction that failed and the exception it threw, stored in PostgreSQL, SQLite or memory. It also resolves junctions from dependency injection and runs pluggable effect providers around each run.
dotnet add package Trax.Effect
dotnet add package Trax.Effect.Data.Postgres # or Trax.Effect.Data.InMemory
dotnet add package Trax.Effect.Provider.Parameter # SaveTrainParameters, in the setup below
dotnet add package Trax.Effect.JunctionProvider.Logging # AddJunctionLoggerWhat It Adds
Everything in Core, plus:
ServiceTrain<TIn, TOut>- extendsTrainwith automatic metadata tracking- Execution metadata - every train run produces a queryable record (state, timing, input/output, errors)
- Dependency injection - junctions resolved from your DI container
- Effect providers - pluggable providers for persistence, logging, and serialization
- Lifecycle hooks - fire on train state transitions (started, completed, failed, cancelled) for notifications, metrics, or real-time updates
- Independent side effects - each provider saves in turn, and one that throws does not stop the others; metadata (state, timing, errors) is persisted regardless of outcome
Train vs ServiceTrain
Train<TIn, TOut> (Core) - The core pipeline engine. Chains junctions, propagates errors, manages Memory. No logging, no DI, no side effects.
ServiceTrain<TIn, TOut> (Effect) - A Train with the services around it. Wraps every run with:
- A run record (start time, end time, success or failure, and the input and output when parameter saving is on)
- Effect providers (database persistence, JSON logging, parameter serialization)
- Integration with
ITrainBusfor dispatch discovery IServiceProvideraccess for junction instantiation
using LanguageExt;
using Trax.Effect.Services.ServiceTrain;
public class CreateUserTrain : ServiceTrain<CreateUserRequest, User>, ICreateUserTrain
{
protected override Task<Either<Exception, User>> Junctions() =>
Chain<ValidateUserJunction>()
.Chain<CreateUserJunction>().Resolve();
}The code inside Junctions() is identical. ServiceTrain adds the infrastructure around it.
The Effect Pattern
Effects are operations that happen as the train passes through its route, provided by pluggable effect providers. Junctions don't write directly to a database or logger. Instead, the train tracks models during the journey, and effect providers handle the actual work at the end:
- On both tracks, effect providers run
SaveChanges, and metadata (state, timing, errors) is always persisted - If the train reaches the right track (success), output is recorded alongside the metadata
- If any junction takes the left track (failure), the exception and failure details are recorded, and the same
SaveChangesruns on every provider. Nothing rolls back: whatever the train tracked before it failed, including entities added to the train'sDataContextand models a custom provider holds, is committed with the failure record.
This gives you full audit trails on every outcome and modularity (add/remove providers without changing train code).
Setup
builder.Services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString) // Persist metadata (returns TraxEffectBuilderWithData)
.SaveTrainParameters() // Include input/output in metadata
.AddJunctionLogger(serializeJunctionData: true) // Log individual junction executions
)
);The AddEffects callback is a Func<TraxEffectBuilder, TraxEffectBuilder>, so the lambda returns the builder from the last chained call. Data provider methods (UsePostgres, UseInMemory) return TraxEffectBuilderWithData, which unlocks data-dependent methods like AddDataContextLogging at compile time.
Remove any line and the train still runs; it just passes through fewer junctions.
When to Use Effect
- Web APIs where you need to know what ran and why it failed
- Services that need execution audit trails
- Any application where you want observability without building custom logging
When you need decoupled dispatch (callers don't know which train handles a request), add Trax.Mediator.
SDK Reference
AddTrax / AddEffects | UsePostgres | SaveTrainParameters | AddJunctionLogger | AddJunctionProgress | AddMediator | AddTraxDashboard | UseTraxDashboard