Dispatch Module¶
The Dispatch module generates strongly-typed command and query dispatch methods from decorated handler methods — zero indirection, method overload resolution IS the dispatch. The generator emits one typed overload per handler, so the C# compiler picks the right one at the call site. Full type safety, no runtime lookup, no mediator pattern.
Quick Start¶
Define command/query records, write handler methods, and mark a static partial class as the dispatch entry point:
using Deepstaging.Dispatch;
using LanguageExt;
// 1. Define a command
public record CreateOrder(string Name, int Quantity) : ICommand;
public record OrderCreated(string Id);
// 2. Write a handler
public static class OrderCommands
{
[CommandHandler]
public static Eff<AppRuntime, OrderCreated> Handle(CreateOrder cmd) =>
from _ in OrderStore.SaveAsync<AppRuntime>(new Order(cmd.Name, cmd.Quantity))
select new OrderCreated("123");
}
// 3. Mark the dispatch module
[DispatchModule]
public static partial class AppDispatch;
The generator produces typed dispatch methods on AppDispatch:
// Dispatch via the command record
var result = await AppDispatch.Dispatch(new CreateOrder("Widget", 5))
.RunAsync(runtime);
// Or use the convenience overload
var result = await AppDispatch.CreateOrder("Widget", 5)
.RunAsync(runtime);
Features¶
| Feature | Description |
|---|---|
| Typed dispatch | One overload per handler — C# compiler resolves the route |
| Commands & Queries | ICommand / IQuery marker interfaces |
| Auto-commit | IAutoCommittable.CommitAsync() called after command handlers (configurable) |
| Validation | Applicative validation with FieldError and structured 422 responses |
| Authorization | [Authorize] on handlers with [AuthPolicies] integration |
| Idempotency | [Idempotent] enforces unique processing per idempotency key |
| Rate limiting | [RateLimit("policy")] applies named rate limit policies to handlers |
| Convenience overloads | AppDispatch.CreateOrder(name, qty) alongside Dispatch(cmd) |
| Tracing | OpenTelemetry activity per dispatch |
What's Next¶
| Page | Description |
|---|---|
| Attributes | [DispatchModule], [CommandHandler], [QueryHandler], [Authorize], marker interfaces |
| Generated Code | Pipeline steps, result types, convenience overloads, tracing, auditing |
| Effects Composition | How handlers return Eff<RT, T>, runtime constraints, composing with [Runtime] and [Uses] |
| Validation | Applicative validation with FieldError, ValidatorType, and structured 422 responses |
| Authorization | [AuthPolicies], [AuthPolicy], RBAC with [Permissions]/[Roles]/[Require], ASP.NET integration |
Diagnostics¶
| ID | Severity | Description |
|---|---|---|
| DSDSP01 | Error | DispatchModule class must be partial |
| DSDSP02 | Error | DispatchModule class must be static |
| DSDSP03 | Error | CommandHandler method must be static |
| DSDSP04 | Error | QueryHandler method must be static |
| DSDSP05 | Warning | CommandHandler has invalid signature (must return Eff<RT, T> with ≥1 parameter) |
| DSDSP06 | Warning | QueryHandler has invalid signature (must return Eff<RT, T> with ≥1 parameter) |
| DSDSP07 | Error | ValidatorType is missing a Validate method with the correct signature |
| DSDSP08 | Error | AuthPolicies class must be static partial |
| DSDSP09 | Error | AuthPolicy method must be static bool (ClaimsPrincipal) |
| DSDSP10 | Error | Validated = true and ValidatorType are mutually exclusive |
| DSDSP11 | Warning | Validated = true but input type has no DataAnnotations or Validate methods |
| DSDSP12 | Error | Custom Validate{Field} method returns wrong type |
| DSDSP13 | Info | Input type has validated fields without custom validators |
| DSDSP14 | Warning | Handler has no [Require], [Authorize], or [Public] attribute |