Configuring Effect Providers

Effect providers handle side effects as a train runs: database writes, logging, serialization. Each provider is independent: add or remove any of them without changing your train code. For the conceptual background, see Effect overview.

Database Persistence (Postgres or InMemory)

Use when: You need to query train history, audit execution, or debug production issues.

// Production
services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres("Host=localhost;Database=app;Username=postgres;Password=pass")
    )
);

// Testing
services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UseInMemory()
    )
);

This persists a Metadata record for each train execution containing:

  • Train name and state (Pending -> InProgress -> Completed/Failed)
  • Start and end timestamps
  • Serialized input and output
  • Exception details if failed
  • Parent train ID for nested trains

See Data Persistence for the full breakdown of both backends, what gets persisted, and DataContext logging.

JSON Effect (AddJson)

Use when: Debugging during development. Logs train state changes to your configured logger.

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .AddJson()
    )
);

This doesn’t persist anything. It just logs. Useful for seeing what’s happening without setting up a database.

See JSON Effect for how change detection works.

Parameter Effect (SaveTrainParameters)

Use when: You need to store train inputs/outputs in the database for later querying or replay.

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres(connectionString)
        .SaveTrainParameters()  // Serializes Input/Output to Metadata
    )
);

Without this, the Input and Output columns in Metadata are null. With it, they contain JSON-serialized versions of your request and response objects. You can control which parameters are saved:

.SaveTrainParameters(configure: cfg =>
{
    cfg.SaveInputs = true;
    cfg.SaveOutputs = false;  // Skip output serialization
})

This configuration can also be changed at runtime from the dashboard’s Effects page.

See Parameter Effect for details, custom serialization options, and configuration properties.

Junction Logger (AddJunctionLogger)

Use when: You want structured logging for individual junction executions inside a train.

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .AddJunctionLogger(serializeJunctionData: true)
    )
);

This hooks into EffectJunction (not base Junction) lifecycle events. Before and after each junction runs, it logs structured JunctionMetadata containing the junction name, input/output types, timing, and Railway state (Right/Left). When serializeJunctionData is true, the junction’s output is also serialized to JSON in the log entry.

Requires junctions to inherit from EffectJunction<TIn, TOut> instead of Junction<TIn, TOut>. See EffectJunction vs Junction.

See Junction Logger for the full JunctionMetadata field reference.

Junction Progress & Cancellation Check (AddJunctionProgress)

Use when: You need per-junction progress visibility in the dashboard and/or the ability to cancel running trains from the dashboard (including cross-server cancellation).

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .AddJunctionProgress()
    )
);

This registers two junction-level effect providers:

  1. CancellationCheckProvider queries the database for Metadata.CancellationRequested before each junction. If true, throws OperationCanceledException, which maps to TrainState.Cancelled.
  2. JunctionProgressProvider writes the junction name and start time to Metadata.CurrentlyRunningJunction and Metadata.JunctionStartedAt before each junction. After the junction, clears both columns.

The cancellation check runs first so a cancelled train never writes progress columns for a junction that won’t execute. Requires junctions to inherit from EffectJunction<TIn, TOut>.

See Junction Progress for the dual-path cancellation architecture and dashboard integration.

Lifecycle Hooks (AddLifecycleHook)

Use when: You want side effects to fire on train state transitions (notifications, metrics, real-time updates) without coupling your train code to those concerns.

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .AddLifecycleHook<SlackNotificationHook>()
    )
);

Lifecycle hooks implement ITrainLifecycleHook and fire at four points: OnStarted, OnCompleted, OnFailed, OnCancelled. Unlike effect providers, hook exceptions are caught and logged, never propagated. A failing Slack webhook will never cause a train to fail.

The Trax.Api.GraphQL package includes a built-in hook (GraphQLSubscriptionHook) that publishes lifecycle events to GraphQL subscriptions over WebSocket. It’s automatically registered by AddTraxGraphQL(). Only trains decorated with [TraxBroadcast] have their events published.

Combining Providers

Providers compose. A typical production setup:

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres(connectionString)             // Persist metadata
        .SaveTrainParameters()                     // Include input/output in metadata
        .AddJunctionLogger(serializeJunctionData: true)    // Log individual junction executions
        .AddJunctionProgress()                         // Junction progress + cancellation check
    )
    .AddMediator(assemblies)                       // Enable train discovery
);

A typical development setup:

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UseInMemory()                             // Fast, no database needed
        .AddJson()                                 // Log state changes
        .AddJunctionLogger()                           // Log junction executions
    )
    .AddMediator(assemblies)
);

SDK Reference


Table of contents


Back to top

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