Skip to main content

Defining Modules

Defining Modules​

Modules inherit from Module<T> when they return a value, or Module when they do not. For synchronous work, use SyncModule<T> or SyncModule respectively.

T is the type of object that your Module will return, and that object can be seen by other Modules (if they depend on it).

public class FindAFileModule : Module<FileInfo>
{
protected override async Task<FileInfo> ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
return context.Files
.Glob("C:\\**\\MyJsonFile.json")
.Single();
}
}

Modules Without Return Values​

For modules that perform actions without returning meaningful data, use the non-generic Module:

public class CleanupModule : Module
{
protected override Task ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
var folder = context.Files.GetFolder("./temp");
folder.Delete();
return Task.CompletedTask;
}
}

For synchronous operations without a return value, use SyncModule:

public class LoggingModule : SyncModule
{
protected override void Execute(
IModuleContext context, CancellationToken cancellationToken)
{
context.Logger.LogInformation("Pipeline executed at {Time}", DateTime.UtcNow);
}
}

Neither non-generic base class requires you to return a result sentinel.

Configuring Module Behavior​

Configure module behaviors such as timeouts, retry policies, skip conditions, and hooks by overriding the Configure(ModuleConfigurationBuilder) method:

public class MyModule : Module<FileInfo>
{
protected override void Configure(ModuleConfigurationBuilder module) => module
.WithTimeout(TimeSpan.FromMinutes(5))
.WithRetry(3)
.WithSkipWhen(_ => !File.Exists("important.json"), "important.json does not exist")
.WithPriority(ModulePriority.High)
.WithExecutionHint(ExecutionHint.IoBound)
.WithTags("build", "critical")
.WithCategory("build")
.DependsOn<RestoreModule>()
.WithIgnoreFailures()
.WithAlwaysRun();

protected override async Task<FileInfo> ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
// Module logic here
}
}

Available Configuration Options​

MethodDescription
.WithTimeout(TimeSpan)Maximum execution time per attempt; must be positive, or Timeout.InfiniteTimeSpan to disable the default timeout
.WithRetry(int, TimeSpan?, Func<Exception, bool>?)Retry attempts, jittered base delay, and optional exception filter
.WithShield(Shield)Custom Kevlar resilience shield for advanced scenarios
.WithSkipWhen(...)Condition to skip the module
.WithRunIf<T>() / .WithRunIf(IRunCondition)Run only when an IRunCondition is satisfied
.WithSkipIf<T>() / .WithSkipIf(IRunCondition)Skip when an IRunCondition is satisfied
.WithIgnoreFailures()Don't fail the pipeline if this module fails
.WithIgnoreFailuresWhen(...)Conditionally ignore failures
.WithAlwaysRun()Run even if the pipeline has failed
.WithNotInParallel(...)Prevent parallel execution globally or for matching constraint keys
.WithPriority(ModulePriority)Set scheduler priority
.WithExecutionHint(ExecutionHint)Select CPU-bound, I/O-bound, or default concurrency limits
.WithTags(...)Add tags used by metadata-based dependencies
.WithCategory(string)Set the module category
.DependsOn<TModule>()Add a required dependency
.DependsOnOptional<TModule>()Add an optional dependency

The fluent configuration is the canonical runtime model. Existing attributes such as [Priority], [ExecutionHint], [NotInParallel], [ModuleTag], [ModuleCategory], and [DependsOn<T>] remain supported as declarative sugar and are merged into the same model.

Lifecycle Hooks​

You can also override lifecycle methods directly on the module class:

public class MyModule : Module<string>
{
protected override Task OnBeforeExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
context.Logger.LogInformation("Starting module execution");
return Task.CompletedTask;
}

protected override Task<ModuleResult<string>?> OnAfterExecuteAsync(
IModuleContext context,
ModuleResult<string> result,
CancellationToken cancellationToken)
{
context.Logger.LogInformation("Module completed with status: {Status}", result.Status);
return Task.FromResult<ModuleResult<string>?>(null);
}

protected override Task OnSkippedAsync(
IModuleContext context,
SkipDecision skipDecision,
CancellationToken cancellationToken)
{
context.Logger.LogWarning("Module skipped: {Reason}", skipDecision.Reason);
return Task.CompletedTask;
}

protected override Task OnFailedAsync(
IModuleContext context,
Exception exception,
CancellationToken cancellationToken)
{
context.Logger.LogError(exception, "Module failed");
return Task.CompletedTask;
}

protected override async Task<string> ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
return "result";
}
}

Tags and Categories​

Organize your modules with tags and categories:

[ModuleCategory("Build")]
[ModuleTag("critical")]
[ModuleTag("fast")]
public class BuildModule : Module<BuildOutput>
{
protected override async Task<BuildOutput> ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
// ...
}
}

Or define them programmatically:

public class BuildModule : Module<BuildOutput>
{
protected override void Configure(ModuleConfigurationBuilder module) => module
.WithCategory("Build")
.WithTags("critical", "fast");

protected override async Task<BuildOutput> ExecuteAsync(
IModuleContext context, CancellationToken cancellationToken)
{
// ...
}
}

See the individual documentation pages for more details on each behavior: