Resolve

Ends a chain, taking the train's TReturn out of Memory. Follows a priority chain: exception > short-circuit value > Memory lookup.

Every chain ends with it. On a chain that names junctions it is the last call in the fluent sequence; on a chain that names none, because the train's return type is already in Memory as the input type or as Unit, it is the only call.

Signature

// On the train: ends a chain that names no junctions
protected Either<Exception, TReturn> Resolve()
 
// On MonadTask<TInput, TReturn>, returned by Chain, IChain and ShortCircuit
public Task<Either<Exception, TReturn>> Resolve()
public Task<Either<Exception, TReturn>> Resolve(Either<Exception, TReturn> returnType)
 
// On Monad<TInput, TReturn>, returned by Extract and AddServices on the train
public Either<Exception, TReturn> Resolve()
public Either<Exception, TReturn> Resolve(Either<Exception, TReturn> returnType)

The train's own Resolve() has no overload taking a value. A declaration says which junctions run, so stating a result directly would let it return something the junctions it names never produced. The chain types Monad and MonadTask do carry a public Resolve(returnType) overload, but a Junctions() that ends in it is refused by the startup chain check, and the host will not start. It exists for code that drives a Monad directly, outside a train's declaration.

Returns

Either<Exception, TReturn> (wrapped in a Task on MonadTask), the train result. Left contains the exception on failure; Right contains the TReturn value on success. A chain whose last step is synchronous (Resolve() on the train, or after Extract with no junction) is wrapped in Task.FromResult to match the Junctions() return type.

Examples

Ending a chain of junctions

protected override Task<Either<Exception, OrderResult>> Junctions() =>
    Chain<ValidateOrder>()
        .Chain<ProcessPayment>()   // Stores OrderResult in Memory
        .Resolve();                // Takes OrderResult back out

A chain that names no junctions

The train's return type is already in Memory, so there is nothing to chain.

public class EchoTrain : ServiceTrain<string, string>, IEchoTrain
{
    protected override Task<Either<Exception, string>> Junctions() =>
        Task.FromResult(Resolve());
}

Resolution Priority

Resolve() follows this order:

  1. Exception: if any junction set an exception, return Left(exception).
  2. Short-circuit value: if a ShortCircuit junction returned Right, return Right(shortCircuitValue).
  3. Memory lookup: take TReturn from Memory by its exact type.
  4. Container: if Memory does not hold it, ask the service provider the chain carries.
  5. Fallback: if neither has it, return Left(TrainException) naming the train and the missing type, for example "Train 'CreateUserTrain' has no 'User' to resolve: nothing in the chain produced one and it is not registered in the container. Chain a junction that outputs 'User' before Resolve().". That holds for a value type too: a missing int or Guid fails rather than resolving to its default.

Remarks

  • Resolve() does not throw. It returns an Either. The calling Run() method unwraps the Either and throws if needed.
  • The parameterized Resolve(returnType) on Monad and MonadTask is simpler: it returns the chain's exception if one exists, otherwise the provided value. It does not check short-circuit or Memory. Inside Junctions() it is refused at startup, as is returning a value without Resolve() at all (Task.FromResult(value)).