IJunctionEffectProvider

A junction effect: code that runs before and after every EffectJunction in a service train. The junction logger and junction progress are junction effects. You write a provider and a factory that creates it, and register the factory with AddJunctionEffect.

Signature

namespace Trax.Effect.Services.JunctionEffectProvider;
 
public interface IJunctionEffectProvider : IDisposable
{
    Task BeforeJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
        EffectJunction<TIn, TOut> effectJunction,
        ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
        CancellationToken cancellationToken
    );
 
    Task AfterJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
        EffectJunction<TIn, TOut> effectJunction,
        ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
        CancellationToken cancellationToken
    );
}
 
namespace Trax.Effect.Services.JunctionEffectProviderFactory;
 
public interface IJunctionEffectProviderFactory
{
    IJunctionEffectProvider Create();
}
ParameterTypeDescription
effectJunctionEffectJunction<TIn, TOut>The junction. Its Metadata already exists.
serviceTrainServiceTrain<TTrainIn, TTrainOut>The train running it, with its own Metadata
cancellationTokenCancellationTokenThe train's token

Lifecycle

CallWhen
IJunctionEffectProviderFactory.Create()When a train's junction effect runner is created, for each factory the effect registry reports enabled at that moment. Toggling an effect affects runners created afterwards.
BeforeJunctionExecutionBefore each EffectJunction runs: after its Metadata is created, before its start time is set
AfterJunctionExecutionAfter each EffectJunction, whether it succeeded, failed, or was skipped because an earlier junction failed. Metadata then holds the end time, the railway state and HasRan.
Dispose()When the train is disposed. An exception is logged and the other providers are still disposed.

Providers run in registration order. A plain Junction does not trigger them.

Failures

An exception from either method is not caught. It stops the providers after it and fails the train. From AfterJunctionExecution that is true even when the junction succeeded, and when the junction had already failed, the provider's exception is what the train reports. Catch inside the provider anything that should not fail a run.

Example

public sealed class SlowJunctionWarning(ILogger<SlowJunctionWarning> logger) : IJunctionEffectProvider
{
    public Task BeforeJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
        EffectJunction<TIn, TOut> junction, ServiceTrain<TTrainIn, TTrainOut> train, CancellationToken ct) =>
        Task.CompletedTask;
 
    public Task AfterJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
        EffectJunction<TIn, TOut> junction, ServiceTrain<TTrainIn, TTrainOut> train, CancellationToken ct)
    {
        if (junction.Metadata is { StartTimeUtc: { } start, EndTimeUtc: { } end } && end - start > TimeSpan.FromSeconds(5))
            logger.LogWarning("{Junction} in {Train} took {Elapsed}", junction.Metadata.Name, train.TrainName, end - start);
        return Task.CompletedTask;
    }
 
    public void Dispose() { }
}
 
public sealed class SlowJunctionWarningFactory(ILogger<SlowJunctionWarning> logger) : IJunctionEffectProviderFactory
{
    public IJunctionEffectProvider Create() => new SlowJunctionWarning(logger);
}
 
services.AddTrax(trax => trax.AddEffects(effects => effects
    .UsePostgres(connectionString)
    .AddJunctionEffect<SlowJunctionWarningFactory>()));

Package

dotnet add package Trax.Effect