WorkflowForge API Reference

Types, interfaces, builders, and extension points in the core library.


Table of Contents


Core Factory

WorkflowForge

Factories for workflows, foundries, and smiths.

public static class WorkflowForge

Methods

CreateWorkflow

public static WorkflowBuilder CreateWorkflow(string? workflowName = null, IServiceProvider? serviceProvider = null)

Creates a WorkflowBuilder. Name and IServiceProvider are optional.

Parameters:

  • workflowName: Optional workflow name (null lets you call WithName later)
  • serviceProvider: Optional DI container for operation resolution

Returns: WorkflowBuilder for fluent configuration

Example:

var workflow = WorkflowForge.CreateWorkflow("OrderProcessing")
    .AddOperation(new ValidateOperation())
    .Build();

CreateFoundry

public static IWorkflowFoundry CreateFoundry(
    string workflowName,
    IWorkflowForgeLogger? logger = null,
    IDictionary<string, object?>? initialProperties = null,
    WorkflowForgeOptions? options = null)

Returns a foundry: logger, initial properties, and options are optional.

Parameters:

  • workflowName: Foundry name for identification
  • logger: Optional logger for the foundry
  • initialProperties: Optional initial properties for the foundry
  • options: Optional execution options for the foundry

Returns: IWorkflowFoundry execution context

Example:

using var foundry = WorkflowForge.CreateFoundry("MyFoundry", logger, initialProperties, options);

CreateSmith

public static IWorkflowSmith CreateSmith(
    IWorkflowForgeLogger? logger = null,
    IServiceProvider? serviceProvider = null,
    WorkflowForgeOptions? options = null)

Returns a smith: logger, IServiceProvider, and options are optional.

Returns: IWorkflowSmith workflow executor

Example:

using var smith = WorkflowForge.CreateSmith();
await smith.ForgeAsync(workflow, foundry);

Workflow Interfaces

IWorkflow

Immutable workflow definition. Dispose when you are done holding native resources (rare).

public interface IWorkflow : IDisposable
{
    Guid Id { get; }
    string Name { get; }
    string? Description { get; }
    string Version { get; }
    IReadOnlyList<IWorkflowOperation> Operations { get; }
}

Properties:

  • Id: Unique identifier for the workflow
  • Name: Human-readable workflow name
  • Description: Optional description for documentation
  • Version: Workflow version string
  • Operations: Ordered collection of operations to execute

WorkflowBuilder

Fluent constructor for IWorkflow.

public sealed class WorkflowBuilder
{
    WorkflowBuilder WithName(string name);
    WorkflowBuilder WithDescription(string? description);
    WorkflowBuilder WithVersion(string version);
    WorkflowBuilder AddOperation(IWorkflowOperation operation);
    WorkflowBuilder AddOperation<T>() where T : class, IWorkflowOperation;
    WorkflowBuilder AddOperation(string name, Func<IWorkflowFoundry, CancellationToken, Task> action, Func<IWorkflowFoundry, CancellationToken, Task>? restoreAction = null);
    WorkflowBuilder AddOperation(string name, Action<IWorkflowFoundry> action, Action<IWorkflowFoundry>? restoreAction = null);
    WorkflowBuilder AddOperations(params IWorkflowOperation[] operations);
    WorkflowBuilder AddOperations(IEnumerable<IWorkflowOperation> operations);
    WorkflowBuilder AddParallelOperations(params IWorkflowOperation[] operations);
    WorkflowBuilder AddParallelOperations(IEnumerable<IWorkflowOperation> operations, int? maxConcurrency = null, TimeSpan? timeout = null, string? name = null);
    IWorkflow Build();
}

Notes:

  • AddOperation<T>() resolves the operation from the provided IServiceProvider.
  • Inline operations are wrapped into ActionWorkflowOperation instances.
  • AddOperations() adds multiple operations to the sequential execution chain.
  • AddParallelOperations() wraps operations in a ForEachWorkflowOperation for parallel execution with shared input.

Operation Interfaces

IWorkflowOperation

Base interface for all workflow operations.

Prefer subclassing WorkflowOperationBase. You get IDs, default restore, hooks, and ForgeAsyncCore without reimplementing the interface.

public interface IWorkflowOperation : IDisposable
{
    Guid Id { get; }
    string Name { get; }
    
    Task<object?> ForgeAsync(
        object? inputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);
    
    Task RestoreAsync(
        object? outputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);
}

Properties:

  • Id: Unique identifier for the operation instance
  • Name: Human-readable operation name

Methods:

ForgeAsync

Task<object?> ForgeAsync(
    object? inputData,
    IWorkflowFoundry foundry,
    CancellationToken cancellationToken = default)

Executes the operation’s main logic.

Parameters:

  • inputData: Optional input data (prefer foundry properties)
  • foundry: Execution context providing services, logging, and properties
  • cancellationToken: Cancellation token

Returns: Operation result (optional)

Tip: Read from and write to foundry.Properties using SetProperty/GetPropertyOrDefault helpers instead of relying on inputData/return values.


RestoreAsync

Task RestoreAsync(
    object? outputData,
    IWorkflowFoundry foundry,
    CancellationToken cancellationToken = default)

Executes compensation logic to undo operation effects.

Parameters:

  • outputData: Output from original ForgeAsync call
  • foundry: Execution context
  • cancellationToken: Cancellation token

Usage: Called automatically by WorkflowSmith if workflow fails. Operations that override RestoreAsync run their compensation logic; operations that use the base class default (no-op) are safely skipped.


WorkflowOperationBase

Abstract base class implementing common operation patterns with lifecycle hooks.

public abstract class WorkflowOperationBase : IWorkflowOperation
{
    public virtual Guid Id { get; } = Guid.NewGuid();
    public abstract string Name { get; }
    
    // Lifecycle hooks (virtual, override to customize)
    protected virtual Task OnBeforeExecuteAsync(object? inputData, IWorkflowFoundry foundry, CancellationToken ct)
        => Task.CompletedTask;
    
    protected virtual Task OnAfterExecuteAsync(object? inputData, object? outputData, IWorkflowFoundry foundry, CancellationToken ct)
        => Task.CompletedTask;
    
    // Core logic (abstract, implement in derived classes)
    protected abstract Task<object?> ForgeAsyncCore(
        object? inputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken);
    
    // Public entry point (orchestrates hooks + core logic)
    public Task<object?> ForgeAsync(
        object? inputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);
    
    public virtual Task RestoreAsync(
        object? outputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);
    
    public virtual void Dispose() { }
}

Features:

  • Per-instance Id
  • Default RestoreAsync is a no-op unless you override
  • Override Dispose only when you hold unmanaged resources

Lifecycle hooks (v2.0+):

  • OnBeforeExecuteAsync, OnAfterExecuteAsync for cross-cutting work
  • Implement ForgeAsyncCore for the real step body

Example with Hooks:

public class AuditedOperation : WorkflowOperationBase
{
    public override string Name => "AuditedOperation";
    
    protected override Task OnBeforeExecuteAsync(object? inputData, IWorkflowFoundry foundry, CancellationToken ct)
    {
        foundry.Logger.LogInformation("Starting operation with input: {Input}", inputData);
        return Task.CompletedTask;
    }
    
    protected override async Task<object?> ForgeAsyncCore(object? inputData, IWorkflowFoundry foundry, CancellationToken ct)
    {
        // Your operation logic here
        return await ProcessAsync(inputData, ct);
    }
    
    protected override Task OnAfterExecuteAsync(object? inputData, object? outputData, IWorkflowFoundry foundry, CancellationToken ct)
    {
        foundry.Logger.LogInformation("Completed operation with result: {Output}", outputData);
        return Task.CompletedTask;
    }
}

Recommended base: WorkflowOperationBase fits most custom steps.


IWorkflowOperation<TInput, TOutput>

Typed input and output. Prefer the foundry for shared context anyway.

public interface IWorkflowOperation<TInput, TOutput> : IWorkflowOperation
{
    Task<TOutput> ForgeAsync(
        TInput input,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);

    Task RestoreAsync(
        TOutput output,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken = default);
}

Use when you want typed inputs and outputs. Most code paths should still pass context through the foundry.


Foundry Interfaces

IWorkflowFoundry

Execution context providing services, state, and logging to operations.

public interface IWorkflowFoundry :
    IWorkflowExecutionContext,
    IWorkflowMiddlewarePipeline,
    IOperationLifecycleEvents,
    IDisposable
{
    Task ForgeAsync(CancellationToken cancellationToken = default);
    void ReplaceOperations(IEnumerable<IWorkflowOperation> operations);
    bool IsFrozen { get; }
}

Properties:

  • ExecutionId: Unique identifier for this foundry execution
  • CurrentWorkflow: Workflow being executed (or null)
  • ServiceProvider: DI container for resolving operation dependencies
  • Logger: Logging abstraction
  • Options: Execution options for the foundry
  • Properties: Thread-safe dictionary for workflow data
  • IsFrozen: True while ForgeAsync is executing (pipeline is immutable)

Notes:

  • Property helpers like SetProperty, GetPropertyOrDefault, and TryGetProperty are extension methods.
  • Operations and middleware cannot be added/removed while ForgeAsync is running.

Foundry WithOperation Extensions (from FoundryPropertyExtensions):

IWorkflowFoundry WithOperation(this IWorkflowFoundry foundry, string name, Func<IWorkflowFoundry, Task> action, Func<IWorkflowFoundry, Task>? restoreAction = null);
IWorkflowFoundry WithOperation(this IWorkflowFoundry foundry, string name, Action<IWorkflowFoundry> action, Action<IWorkflowFoundry>? restoreAction = null);

Adds inline operations to the foundry. The optional restoreAction provides compensation logic when the workflow fails.


IWorkflowMiddlewarePipeline

Pipeline builder for operations and middleware.

public interface IWorkflowMiddlewarePipeline
{
    void AddOperation(IWorkflowOperation operation);
    void AddMiddleware(IWorkflowOperationMiddleware middleware);
    void AddMiddlewares(IEnumerable<IWorkflowOperationMiddleware> middlewares);
}

Smith Interfaces

IWorkflowSmith

Workflow executor responsible for orchestrating operation execution.

public interface IWorkflowSmith :
    IWorkflowLifecycleEvents,
    ICompensationLifecycleEvents,
    IDisposable
{
    Task ForgeAsync(IWorkflow workflow, CancellationToken cancellationToken = default);
    Task ForgeAsync(IWorkflow workflow, ConcurrentDictionary<string, object?> data, CancellationToken cancellationToken = default);
    Task ForgeAsync(IWorkflow workflow, IWorkflowFoundry foundry, CancellationToken cancellationToken = default);
    IWorkflowFoundry CreateFoundry(IWorkflowForgeLogger? logger = null, IServiceProvider? serviceProvider = null);
    IWorkflowFoundry CreateFoundryFor(IWorkflow workflow, IWorkflowForgeLogger? logger = null, IServiceProvider? serviceProvider = null);
    IWorkflowFoundry CreateFoundryWithData(ConcurrentDictionary<string, object?> data, IWorkflowForgeLogger? logger = null, IServiceProvider? serviceProvider = null);
    void AddWorkflowMiddleware(IWorkflowMiddleware middleware);
}

Methods:

  • ForgeAsync(...): Executes a workflow using one of the three patterns (smith-managed, data dictionary, or reusable foundry).
  • CreateFoundry(...): Creates a reusable foundry with optional logger/service provider.
  • CreateFoundryFor(...): Creates a foundry pre-associated with a workflow.
  • CreateFoundryWithData(...): Creates a foundry with an initial data dictionary.
  • AddWorkflowMiddleware(...): Adds workflow-level middleware around the entire execution.

Example:

using var smith = WorkflowForge.CreateSmith();
using var foundry = smith.CreateFoundryFor(workflow);

await smith.ForgeAsync(workflow, foundry);

Event Interfaces

IWorkflowLifecycleEvents

Workflow-level lifecycle events.

public interface IWorkflowLifecycleEvents
{
    event EventHandler<WorkflowStartedEventArgs>? WorkflowStarted;
    event EventHandler<WorkflowCompletedEventArgs>? WorkflowCompleted;
    event EventHandler<WorkflowFailedEventArgs>? WorkflowFailed;
}

Implemented By: IWorkflowSmith

Events:

  • WorkflowStarted: raised immediately before the first operation runs
  • WorkflowCompleted: raised after the last operation succeeds
  • WorkflowFailed: raised when execution aborts with an error

Example:

smith.WorkflowStarted += (s, e) => 
    Console.WriteLine($"Started: {e.Foundry.CurrentWorkflow?.Name}");
smith.WorkflowCompleted += (s, e) => 
    Console.WriteLine($"Completed in {e.Duration.TotalMilliseconds}ms");

IOperationLifecycleEvents

Operation-level lifecycle events.

public interface IOperationLifecycleEvents
{
    event EventHandler<OperationStartedEventArgs>? OperationStarted;
    event EventHandler<OperationCompletedEventArgs>? OperationCompleted;
    event EventHandler<OperationFailedEventArgs>? OperationFailed;
}

Implemented By: IWorkflowFoundry

Events:

  • OperationStarted: raised as each operation begins
  • OperationCompleted: raised after a successful operation finishes
  • OperationFailed: raised when an operation throws or faults

Example:

foundry.OperationStarted += (s, e) => 
    Console.WriteLine($"Operation {e.Operation.Name} started");
foundry.OperationCompleted += (s, e) => 
    Console.WriteLine($"Operation {e.Operation.Name} completed");

ICompensationLifecycleEvents

Compensation (saga pattern) lifecycle events.

public interface ICompensationLifecycleEvents
{
    event EventHandler<CompensationTriggeredEventArgs>? CompensationTriggered;
    event EventHandler<CompensationCompletedEventArgs>? CompensationCompleted;
    event EventHandler<OperationRestoreStartedEventArgs>? OperationRestoreStarted;
    event EventHandler<OperationRestoreCompletedEventArgs>? OperationRestoreCompleted;
    event EventHandler<OperationRestoreFailedEventArgs>? OperationRestoreFailed;
}

Implemented By: IWorkflowSmith

Events:

  • CompensationTriggered: raised when a failure kicks off the compensation pass
  • CompensationCompleted: raised after every RestoreAsync attempt finishes
  • OperationRestoreStarted: raised immediately before a given RestoreAsync
  • OperationRestoreCompleted: raised after RestoreAsync succeeds
  • OperationRestoreFailed: raised when RestoreAsync throws

Example:

smith.CompensationTriggered += (s, e) => 
    Console.WriteLine($"Compensating: {e.Reason} (failed: {e.FailedOperationName})");
smith.OperationRestoreStarted += (s, e) => 
    Console.WriteLine($"Rolling back {e.Operation.Name}");

Core Options

public sealed class WorkflowForgeOptions : WorkflowForgeOptionsBase
{
    public bool Enabled { get; set; } = true;
    public string SectionName { get; }
    public int MaxConcurrentWorkflows { get; set; } = 0;
    public bool ContinueOnError { get; set; } = false;
    public bool FailFastCompensation { get; set; } = false;
    public bool ThrowOnCompensationError { get; set; } = false;
    public bool EnableOutputChaining { get; set; } = true;
    public override IList<string> Validate();
    public override object Clone();
}

Notes:

  • WorkflowForgeOptions inherits from WorkflowForgeOptionsBase for consistent Enabled, SectionName, Validate(), and Clone() behavior.

Testing Support

FakeWorkflowFoundry

Test double implementing IWorkflowFoundry.

Package: WorkflowForge.Testing

public class FakeWorkflowFoundry : IWorkflowFoundry
{
    // Configurable properties
    public Guid ExecutionId { get; set; }
    public ConcurrentDictionary<string, object?> Properties { get; }
    public IWorkflowForgeLogger Logger { get; set; }
    public WorkflowForgeOptions Options { get; set; }
    public IServiceProvider? ServiceProvider { get; set; }
    
    // Test assertions
    public IReadOnlyList<IWorkflowOperation> Operations { get; }
    public IReadOnlyList<IWorkflowOperationMiddleware> Middlewares { get; }
    public IReadOnlyList<IWorkflowOperation> ExecutedOperations { get; }
    
    // Test helpers
    public void Reset();
    public void TrackExecution(IWorkflowOperation operation);
}

Behavior:

  • Records operations for assertions
  • Swap logger, options, and IServiceProvider
  • Reset() clears state between tests

Example Usage:

[Fact]
public async Task MyOperation_Should_SetProperty()
{
    // Arrange
    var foundry = new FakeWorkflowFoundry();
    var operation = new MyCustomOperation();
    
    // Act
    await operation.ForgeAsync("input", foundry, CancellationToken.None);
    
    // Assert
    Assert.True(foundry.Properties.ContainsKey("myKey"));
    Assert.Equal("expectedValue", foundry.Properties["myKey"]);
}

[Fact]
public async Task Workflow_Should_ExecuteAllOperations()
{
    // Arrange
    var foundry = new FakeWorkflowFoundry();
    foundry.AddOperation(new StepOneOperation());
    foundry.AddOperation(new StepTwoOperation());
    
    // Act
    await foundry.ForgeAsync();
    
    // Assert
    Assert.Equal(2, foundry.ExecutedOperations.Count);
}

Extension Interfaces

IWorkflowOperationMiddleware

Middleware for operation execution pipeline.

public interface IWorkflowOperationMiddleware
{
    Task<object?> ExecuteAsync(
        IWorkflowOperation operation,
        IWorkflowFoundry foundry,
        object? inputData,
        Func<CancellationToken, Task<object?>> next,
        CancellationToken cancellationToken);
}

Methods:

ExecuteAsync

Task<object?> ExecuteAsync(
    IWorkflowOperation operation,
    IWorkflowFoundry foundry,
    object? inputData,
    Func<CancellationToken, Task<object?>> next,
    CancellationToken cancellationToken)

Executes middleware logic in the operation pipeline.

Parameters:

  • operation: Operation being executed
  • foundry: Execution context
  • next: Delegate to call next middleware/operation
  • cancellationToken: Cancellation token

Returns: Operation result

Example:

public class TimingMiddleware : IWorkflowOperationMiddleware
{
    public async Task<object?> ExecuteAsync(
        IWorkflowOperation operation,
        IWorkflowFoundry foundry,
        object? inputData,
        Func<CancellationToken, Task<object?>> next,
        CancellationToken cancellationToken)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            return await next(cancellationToken);
        }
        finally
        {
            sw.Stop();
            _logger.LogInformation("Operation {Op} took {Ms}ms", 
                operation.Name, sw.ElapsedMilliseconds);
        }
    }
}

IWorkflowForgeLogger

Logging abstraction for framework and operations.

public interface IWorkflowForgeLogger
{
    void LogTrace(string message, params object[] args);
    void LogDebug(string message, params object[] args);
    void LogInformation(string message, params object[] args);
    void LogWarning(string message, params object[] args);
    void LogError(Exception? exception, string message, params object[] args);
    void LogCritical(Exception? exception, string message, params object[] args);
}

Standard levels: Trace through Critical

Shipped implementations: ConsoleLogger, NullLogger, plus Serilog via the logging extension


ISystemTimeProvider

Time abstraction for testability.

public interface ISystemTimeProvider
{
    DateTimeOffset UtcNow { get; }
}

Lets tests fix the clock. Production code normally uses SystemTimeProvider.

Example:

public class TimeSensitiveOperation : WorkflowOperationBase
{
    private readonly ISystemTimeProvider _timeProvider;
    
    public TimeSensitiveOperation(ISystemTimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }
    
    protected override async Task<object?> ForgeAsyncCore(
        object? inputData,
        IWorkflowFoundry foundry,
        CancellationToken cancellationToken)
    {
        var now = _timeProvider.UtcNow;
        // Time-dependent logic
        return inputData;
    }
}

Built-In Operations

DelayOperation

Introduces a delay in workflow execution.

public class DelayOperation : WorkflowOperationBase
{
    public DelayOperation(TimeSpan delay);
}

Example:

var workflow = WorkflowForge.CreateWorkflow("DelayedWorkflow")
    .AddOperation(new DelayOperation(TimeSpan.FromSeconds(2)))
    .Build();

LoggingOperation

Logs a message during workflow execution.

public class LoggingOperation : WorkflowOperationBase
{
    public LoggingOperation(string message, WorkflowForgeLogLevel logLevel = WorkflowForgeLogLevel.Information, string? name = null);
}

Example:

var workflow = WorkflowForge.CreateWorkflow("LoggingWorkflow")
    .AddOperation(new LoggingOperation("Starting workflow"))
    .Build();

ConditionalWorkflowOperation

Executes different operations based on a condition.

public class ConditionalWorkflowOperation : WorkflowOperationBase
{
    // Async condition with input data
    public ConditionalWorkflowOperation(
        Func<object?, IWorkflowFoundry, CancellationToken, Task<bool>> condition,
        IWorkflowOperation trueOperation,
        IWorkflowOperation? falseOperation = null,
        string? name = null,
        Guid? id = null);

    // Sync condition with input data
    public ConditionalWorkflowOperation(
        Func<object?, IWorkflowFoundry, bool> condition,
        IWorkflowOperation trueOperation,
        IWorkflowOperation? falseOperation = null,
        string? name = null,
        Guid? id = null);

    // Simple condition (foundry-only) via factory
    public static ConditionalWorkflowOperation Create(
        Func<IWorkflowFoundry, bool> condition,
        IWorkflowOperation trueOperation,
        IWorkflowOperation? falseOperation = null,
        string? name = null);
}

Example:

var conditionalOp = new ConditionalWorkflowOperation(
    condition: (input, f) => f.GetPropertyOrDefault<decimal>("Amount") > 100,
    trueOperation: new HighValueOperation(),
    falseOperation: new StandardOperation());

ForEachWorkflowOperation

Executes multiple operations concurrently with configurable data distribution.

public sealed class ForEachWorkflowOperation : WorkflowOperationBase
{
    // Factory methods
    public static ForEachWorkflowOperation CreateSharedInput(
        IEnumerable<IWorkflowOperation> operations,
        TimeSpan? timeout = null,
        int? maxConcurrency = null,
        string? name = null);
    
    public static ForEachWorkflowOperation CreateSplitInput(
        IEnumerable<IWorkflowOperation> operations,
        TimeSpan? timeout = null,
        int? maxConcurrency = null,
        string? name = null);
    
    public static ForEachWorkflowOperation CreateNoInput(
        IEnumerable<IWorkflowOperation> operations,
        TimeSpan? timeout = null,
        int? maxConcurrency = null,
        string? name = null);
}

Data Strategies:

  • CreateSharedInput: All operations receive the same input data
  • CreateSplitInput: Input collection is split and distributed among operations
  • CreateNoInput: Operations receive null input

Example:

// Execute multiple operations in parallel with shared input
var forEachOp = ForEachWorkflowOperation.CreateSharedInput(
    new IWorkflowOperation[] { new ProcessA(), new ProcessB(), new ProcessC() },
    maxConcurrency: 4,
    name: "ProcessAll");

// Throttled execution with 2 concurrent operations max
var throttledOp = ForEachWorkflowOperation.CreateSplitInput(
    operations,
    maxConcurrency: 2);