TaskHub.Shared

TaskHub.Shared.Commands - Technical Manual

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.

🏛 Deep Architecture

1. The CommandsBus & Invoker Cache

The CommandsBus is the orchestrator of the pipeline. To achieve near-native performance, it avoids repeated reflection by using a Compiled Invoker Cache:

2. The Execution Onion

The pipeline follows a specific recursive structure:

  1. Async Scope Creation: Every command starts by creating a new IServiceScope. All dependencies (including the handler and behaviors) are resolved within this scope.
  2. Pre-Behaviors: Executed from the outermost to the innermost. Each behavior can short-circuit the entire pipeline by returning a Result without calling the next delegate.
  3. Command Handler: The core of the onion. It receives the command and returns the initial result.
  4. Post-Transformers: Executed after the handler returns. They can modify the result object (e.g., adding metadata or wrapping it).
  5. Post-Behaviors: Executed last. These are “fire-and-forget” relative to the result—they cannot modify the result and are used for side effects.

🛠 API Reference

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. |


🚀 Complex Implementation Example

Scenario: Creating a Task with Validation and Auditing

1. Define Command and Result

public record CreateTaskCommand(string Title, string Description) : ICommand;
public record TaskResult(Guid Id) : Result(0, "Success");

2. Implement Validation (Pre-Behavior)

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);
    }
}

3. Implement Command Handler

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);
    }
}

4. Implement Audit (Post-Behavior)

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;
    }
}

👁 Telemetry & Diagnostics

OpenTelemetry Spans

The CommandsBus creates highly descriptive spans:

Tags

Metrics


✅ Best Practices & Anti-Patterns

🟢 Best Practices

🔴 Anti-Patterns