The TaskHub.Shared.Commands module provides a high-performance, behavior-driven CQRS pipeline. It decouples command definition from execution and enables seamless integration of cross-cutting concerns like validation, logging, and transactional integrity.
The CommandsBus is the orchestrator of the pipeline. To achieve near-native performance, it avoids repeated reflection by using a Compiled Invoker Cache:
TCommand and TResult pair, the bus scans the DI container for all related behaviors (IPreBehavior, ICommandHandler, IPostTransformer, IPostBehavior).Func<IServiceProvider, TCommand, CancellationToken, Task<TResult>> is stored in a ConcurrentDictionary. Subsequent calls for the same command type are direct delegate invocations.The pipeline follows a specific recursive structure:
IServiceScope. All dependencies (including the handler and behaviors) are resolved within this scope.Result without calling the next delegate.ICommandsBus| Method | Description |
| :— | :— |
| Task<TResult> SendAsync<TCommand, TResult>(TCommand command, CancellationToken ct) | Entry point for command execution. |
ICommandHandler<TCommand, TResult>| Method | Description |
| :— | :— |
| Task<TResult> HandleAsync(TCommand command, CancellationToken ct) | Implement this to execute the business logic of a command. |
IPreBehavior<TCommand, TResult>| Method | Description |
| :— | :— |
| Task<TResult> HandleAsync(TCommand command, Func<TCommand, CancellationToken, Task<TResult>> next, CancellationToken ct) | Wraps the inner pipeline. Must call next to continue. |
public record CreateTaskCommand(string Title, string Description) : ICommand;
public record TaskResult(Guid Id) : Result(0, "Success");
public class ValidationBehavior<TCommand, TResult> : IPreBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<TResult> HandleAsync(TCommand cmd, Func<TCommand, CancellationToken, Task<TResult>> next, CancellationToken ct)
{
if (cmd is CreateTaskCommand { Title.Length: < 5 })
return (TResult)ResultFactory.OnFailed(400, "Title too short");
return await next(cmd, ct);
}
}
public class CreateTaskHandler(IUnitOfWork uow) : ICommandHandler<CreateTaskCommand, TaskResult>
{
public async Task<TaskResult> HandleAsync(CreateTaskCommand cmd, CancellationToken ct)
{
var id = Guid.NewGuid();
// ... logic to add task
await uow.SaveAsync(ct);
return new TaskResult(id);
}
}
public class AuditBehavior<TCommand, TResult>(ILogger<AuditBehavior<TCommand, TResult>> logger)
: IPostBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public Task HandleAsync(TCommand cmd, TResult res, CancellationToken ct)
{
if (res.IsSuccess)
logger.LogInformation("Command {Name} succeeded", typeof(TCommand).Name);
return Task.CompletedTask;
}
}
The CommandsBus creates highly descriptive spans:
CommandsBus: The root span for the entire operation.[PRE] {BehaviorName}: Span for each pre-behavior.[HNDLR] {HandlerName}: Span for the core handler.[TRANS] {TransformerName}: Span for each transformer.[POST] {BehaviorName}: Span for each post-behavior.command.name: The type name of the command.actor.type: e.g., handler, pre, post.actor.name: The class name of the component being executed.result.code: The integer code returned by the operation.commands_execution_duration_seconds: Histogram measuring the total time from SendAsync to return.[Order] Attribute: Control the execution order of behaviors explicitly. Lower numbers run first (outermost).ICommandsBus into Handlers: Avoid “Command nesting.” If a command needs another, consider if they should be part of the same transaction or if the logic should be in a shared service.