Configuration

All Trax services are configured through a single entry point: AddTrax. This method accepts a lambda that receives a TraxBuilder, which provides a fluent API for adding effects, the mediator, and the scheduler.

The builder uses a step builder pattern that enforces ordering at compile time. Each configuration step returns a different type that only exposes valid next steps:

  1. TraxBuilder – the initial builder, exposes AddEffects()
  2. TraxBuilderWithEffects – returned by AddEffects(), exposes AddMediator()
  3. TraxBuilderWithMediator – returned by AddMediator(), exposes AddScheduler()

This means you cannot call AddMediator() without first calling AddEffects(), and you cannot call AddScheduler() without first calling AddMediator(). The compiler catches incorrect ordering before you run your application.

Effect-specific methods are nested inside .AddEffects(effects => ...), which receives a TraxEffectBuilder. The AddEffects lambda must return the builder from the last chained call (Func<TraxEffectBuilder, TraxEffectBuilder>).

Data provider methods (UsePostgres, UseInMemory) return TraxEffectBuilderWithData, a subclass of TraxEffectBuilder that unlocks data-dependent methods like AddDataContextLogging(). This provides compile-time safety: you cannot call AddDataContextLogging() without first configuring a data provider.

Entry Point

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres(connectionString)
        .AddDataContextLogging()
        .AddJson()
        .SaveTrainParameters()
        .AddJunctionLogger()
        .AddJunctionProgress()
    )
    .AddMediator(typeof(Program).Assembly)
    .AddScheduler(scheduler => scheduler
        .MaxActiveJobs(10)
    )
);

AddEffects Signature

// With configuration
public static TraxBuilderWithEffects AddEffects(
    this TraxBuilder builder,
    Func<TraxEffectBuilder, TraxEffectBuilder> configure
)

// Parameterless defaults (no data provider)
public static TraxBuilderWithEffects AddEffects(
    this TraxBuilder builder
)

The AddEffects callback is a Func, so the lambda must return the builder from the last chained call. For expression-body lambdas (the common case), this happens naturally:

.AddEffects(effects => effects.UsePostgres(connectionString).AddJson())

For multi-statement lambdas, explicitly return the builder:

.AddEffects(effects =>
{
    effects.ServiceCollection.AddSingleton(myService);
    return effects.UsePostgres(connectionString).AddJson();
})

The parameterless overload registers effects with no data provider. Features that require a data provider (such as AddScheduler() or AddJunctionProgress()) will throw at build time with a helpful error message.

AddTrax Signature

public static IServiceCollection AddTrax(
    this IServiceCollection services,
    Action<TraxBuilder> configure
)

AddTrax registers a TraxMarker singleton in the DI container. This marker is checked at runtime by AddTraxDashboard() and AddTraxGraphQL() – if the marker is missing, they throw InvalidOperationException with a message directing you to call AddTrax() first.

Step Builder Types

Type Returned By Exposes
TraxBuilder AddTrax() lambda AddEffects()
TraxBuilderWithEffects AddEffects() AddMediator()
TraxBuilderWithMediator AddMediator(), AddScheduler() AddScheduler()

Effect Builder Types

Inside the AddEffects() callback, data provider methods return a more specific type:

Type Returned By Exposes
TraxEffectBuilder AddEffects() lambda SkipMigrations(), UsePostgres(), UseSqlite(), UseInMemory(), AddJson(), SaveTrainParameters(), AddJunctionLogger(), AddJunctionProgress(), SetEffectLogLevel(), UseBroadcaster()
TraxEffectBuilderWithData UsePostgres(), UseSqlite(), UseInMemory() Everything on TraxEffectBuilder plus AddDataContextLogging()

Generic effect methods (AddJson, SaveTrainParameters, AddJunctionLogger, AddJunctionProgress, SetEffectLogLevel, UseBroadcaster) preserve the concrete builder type through chaining. If you start with TraxEffectBuilderWithData, it stays TraxEffectBuilderWithData.

Ordering Enforcement

The step builder pattern provides two levels of ordering enforcement:

Compile-Time (Step Builder)

The fluent chain AddEffects() -> AddMediator() -> AddScheduler() is enforced by the type system. Each method returns a different builder type that only exposes valid next steps. If you try to call methods out of order, the code will not compile:

// Compiles -- correct order
services.AddTrax(trax => trax
    .AddEffects(effects => effects.UsePostgres(connectionString))
    .AddMediator(typeof(Program).Assembly)
    .AddScheduler()
);

// Compiles -- AddDataContextLogging() is available because UsePostgres() returns TraxEffectBuilderWithData
services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres(connectionString)
        .AddDataContextLogging()
    )
    .AddMediator(typeof(Program).Assembly)
);

// Does NOT compile -- AddDataContextLogging() requires TraxEffectBuilderWithData
services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .AddJson()
        .AddDataContextLogging()  // Error: AddDataContextLogging is not available on TraxEffectBuilder
    )
    .AddMediator(typeof(Program).Assembly)
);

// Does NOT compile -- AddMediator() is not available on TraxBuilder
services.AddTrax(trax => trax
    .AddMediator(typeof(Program).Assembly)  // Error: TraxBuilder has no AddMediator
);

// Does NOT compile -- AddScheduler() is not available on TraxBuilderWithEffects
services.AddTrax(trax => trax
    .AddEffects(effects => effects.UsePostgres(connectionString))
    .AddScheduler()  // Error: TraxBuilderWithEffects has no AddScheduler
);

Runtime (TraxMarker)

AddTraxDashboard() and AddTraxGraphQL() are called on IServiceCollection / WebApplicationBuilder, not on the step builder chain. They perform a runtime check instead: if AddTrax() was not called first, they throw InvalidOperationException with a clear message:

InvalidOperationException: AddTrax() must be called before AddTraxDashboard().
Call services.AddTrax(...) in your service configuration before calling AddTraxDashboard().

Builder Properties

These properties can be set directly on the TraxEffectBuilder:

Property Type Default Description
ServiceCollection IServiceCollection (from constructor) Direct access to the DI container for manual registrations
SerializeJunctionData bool false Whether junction input/output data should be serialized globally
LogLevel LogLevel LogLevel.Debug Minimum log level for effect logging
TrainParameterJsonSerializerOptions JsonSerializerOptions TraxJsonSerializationOptions.Default System.Text.Json options for parameter serialization
NewtonsoftJsonSerializerSettings JsonSerializerSettings TraxJsonSerializationOptions.NewtonsoftDefault Newtonsoft.Json settings for legacy serialization

Extension Methods

Method Description
SkipMigrations Disables automatic database migration in UsePostgres() for Lambda/serverless environments
UsePostgres Adds PostgreSQL database support for metadata persistence
UseInMemory Adds in-memory database support for testing/development
AddDataContextLogging Enables logging for database operations
AddJson Adds JSON change detection for tracking model mutations
SaveTrainParameters Serializes train input/output to JSON for persistence (optionally configurable)
AddJunctionLogger Adds per-junction execution logging
AddJunctionProgress Adds junction progress tracking and cross-server cancellation checking
AddMediator Registers the TrainBus and discovers trains via assembly scanning. Accepts params Assembly[] shorthand or Func<TraxMediatorBuilder, TraxMediatorBuilder> for full control (custom lifetime, multiple assemblies). Called on TraxBuilderWithEffects, returns TraxBuilderWithMediator
AddEffect / AddJunctionEffect Registers custom effect provider factories
AddLifecycleHook Registers lifecycle hooks that fire on train state transitions
SetEffectLogLevel Sets the minimum log level for effect logging

Table of contents


Back to top

Trax - A .NET framework for Railway Oriented Programming with Effects, Scheduling, and more