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 — Before each junction, queries the database for Metadata.CancellationRequested. If true, throws OperationCanceledException, which maps to TrainState.Cancelled.
  2. JunctionProgressProvider — Before each junction, writes the junction name and start time to Metadata.CurrentlyRunningJunction and Metadata.JunctionStartedAt. 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

> UsePostgres | UseInMemory | AddJson | SaveTrainParameters | AddJunctionLogger | AddJunctionProgress | AddLifecycleHook | AddMediator