Enterprise-Grade CQRS Mediator for .NET
A production-ready CQRS mediator library for .NET with Result pattern, pipeline behaviors, DDD support, and attribute-based HTTP endpoint mapping.
- Result Pattern built-in — No more throwing exceptions for control flow
- Explicit CQRS —
ICommand<T>,IQuery<T>instead of justIRequest - 10 built-in behaviors — Audit, logging, validation, auth, transactions, retry, caching, performance, idempotency, cache invalidation
- Attribute-based HTTP endpoints —
[HttpEndpoint]eliminates controller boilerplate - DDD native —
IDomainEvent, aggregate root patterns - Publish performance — Up to 66% faster notification fanout with 4.7x less memory (benchmarks)
- 221 tests — Unit, integration, load, E2E
Benchmarked against MediatR v12 using BenchmarkDotNet. Full results: docs/BENCHMARKS.md
| Handlers | Qorpe | MediatR v12 | Result |
|---|---|---|---|
| 1 handler | 24 ns / 88 B | 46 ns / 288 B | 48% faster, 3.3x less memory |
| 10 handlers | 69 ns / 376 B | 187 ns / 1,656 B | 63% faster, 4.4x less memory |
| 100 handlers | 532 ns / 3,256 B | 1,552 ns / 15,336 B | 66% faster, 4.7x less memory |
| Behaviors | Qorpe | MediatR v12 | Result |
|---|---|---|---|
| 1 behavior | 61 ns / 288 B | 59 ns / 368 B | ~equal speed, 22% less memory |
| 3 behaviors | 89 ns / 560 B | 90 ns / 656 B | Qorpe 1% faster, 15% less memory |
| 5 behaviors | 119 ns / 832 B | 118 ns / 944 B | ~equal speed, 12% less memory |
dotnet add package Qorpe.Mediatorbuilder.Services.AddQorpeMediator(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));public record CreateOrderCommand(string UserId, List<OrderItem> Items)
: ICommand<Result<Guid>>;public class CreateOrderHandler : ICommandHandler<CreateOrderCommand, Result<Guid>>
{
public ValueTask<Result<Guid>> Handle(CreateOrderCommand request, CancellationToken ct)
{
var orderId = Guid.NewGuid();
// ... create order
return new ValueTask<Result<Guid>>(Result<Guid>.Success(orderId));
}
}var result = await mediator.Send(new CreateOrderCommand("user-1", items));
result.Match(
id => Console.WriteLine($"Order created: {id}"),
error => Console.WriteLine($"Failed: {error}")
);// Commands — change state
public record CreateOrder(string UserId) : ICommand<Result<Guid>>;
public record CancelOrder(Guid Id) : ICommand<Result>;
// Queries — read state, no side effects
public record GetOrderById(Guid Id) : IQuery<Result<Order>>;
// Streaming
public record SearchOrders(string? Status) : IStreamRequest<Order>;// Success
return Result<Guid>.Success(orderId);
// Failure with typed errors
return Error.NotFound("Order.NotFound", "Order not found");
// Functional operations
var name = result.Map(order => order.Name)
.Bind(name => ValidateName(name))
.Match(
name => $"Hello, {name}",
error => $"Error: {error.Description}");public record OrderCreatedEvent(Guid OrderId, string UserId) : IDomainEvent
{
public DateTimeOffset OccurredOn { get; } = DateTimeOffset.UtcNow;
}
// Multiple handlers per event
public class SendEmail : INotificationHandler<OrderCreatedEvent> { ... }
public class UpdateInventory : INotificationHandler<OrderCreatedEvent> { ... }
await publisher.Publish(new OrderCreatedEvent(orderId, userId));[HttpEndpoint("POST", "/api/orders", Tags = new[] { "Orders" })]
[Transactional]
[Auditable]
public record CreateOrderCommand : ICommand<Result<Guid>> { ... }
// In Program.cs
app.MapQorpeEndpoints(typeof(Program).Assembly);
// Result auto-mapped: Success->200/201, Validation->400, NotFound->404, etc.All behaviors are attribute-driven, configurable, and automatically ordered via IBehaviorOrder.
| # | Behavior | Attribute | Order | Description |
|---|---|---|---|---|
| 1 | Audit | [Auditable] |
100 | Async batching, store abstraction, sensitive data masking |
| 2 | Logging | Auto | 200 | Structured logging, auto-mask, circular reference safe |
| 3 | UnhandledException | Auto | 300 | Catch-all safety net, always re-throws |
| 4 | Authorization | [Authorize] |
400 | Role + policy checking, Result-based responses |
| 5 | Validation | Auto | 500 | FluentValidation multi-validator, Result.Failure |
| 6 | Idempotency | [Idempotent] |
600 | SHA256 key, per-key locking, window-based expiry |
| 7 | Transaction | [Transactional] |
700 | Command-only, rollback, distinct commit/handler errors |
| 8 | Performance | [PerformanceThreshold] |
800 | Per-request thresholds, 30s hard ceiling |
| 9 | Retry | [Retryable] |
900 | Exponential backoff with jitter, success attempt logging |
| 10 | Caching | [Cacheable] |
1000 | Query-only, bounded lock pool, stampede prevention |
| 11 | Cache Invalidation | [InvalidatesCache] |
1001 | Command-driven cache invalidation by key prefix |
builder.Services.AddQorpeMediator(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.NotificationPublishStrategy = NotificationPublishStrategy.Parallel;
cfg.EnablePolymorphicNotifications = true;
cfg.ValidateOnStartup = true;
});
builder.Services.AddQorpeValidation(typeof(Program).Assembly);
builder.Services.AddQorpeAllBehaviors(opts =>
{
opts.ConfigureLogging = log =>
{
log.MaskProperties.Add("CardNumber");
log.MaxSerializedLength = 4096;
};
opts.ConfigurePerformance = perf =>
{
perf.WarningThresholdMs = 500;
perf.CriticalThresholdMs = 5000;
};
});| Feature | MediatR v12 | Qorpe.Mediator |
|---|---|---|
| License | Commercial (2025+) | MIT |
| Publish Performance | Baseline | Up to 66% faster |
| Publish Memory | Baseline | Up to 4.7x less |
| Result Pattern | No (exceptions) | Built-in Result<T> |
| CQRS Types | IRequest only | ICommand, IQuery, IRequest |
| Domain Events | INotification | IDomainEvent + INotification |
| Pre/Post Processors | Defined, wired | Defined, wired |
| Behavior Ordering | Registration order | Explicit IBehaviorOrder |
| Polymorphic Notifications | No | Opt-in |
| Startup Validation | No | ValidateOnStartup |
| Built-in Behaviors | 0 | 11 |
| HTTP Endpoints | No | [HttpEndpoint] attribute |
| Cache Invalidation | No | [InvalidatesCache] attribute |
| ValueTask | No (Task) | Yes (ValueTask) |
| Cancellation Diagnostics | No | Pipeline stage tracking |
| Stream Pipeline Behaviors | No | IStreamPipelineBehavior |
| Sensitive Data | No | [SensitiveData] auto-mask |
| Telemetry | Yes | None |
| Layer | Tests | What It Covers |
|---|---|---|
| Unit | 182 | Result, Error, Guard, Mediator, all behaviors, notifications, validation, pre/post processors |
| Integration | 21 | Full pipeline E2E, HTTP endpoints, cross-behavior, DI registration |
| Load | 18 | 50K concurrent, 500K sequential, memory stability, streaming, latency percentiles |
| Total | 221 | Production-grade coverage |
| Package | Description |
|---|---|
Qorpe.Mediator |
Core — CQRS abstractions, Result pattern, Mediator implementation |
Qorpe.Mediator.Behaviors |
11 built-in pipeline behaviors |
Qorpe.Mediator.FluentValidation |
FluentValidation integration — auto-discovery, multi-validator |
Qorpe.Mediator.AspNetCore |
HTTP endpoint mapping — [HttpEndpoint], Result-to-HTTP, OpenAPI |
Qorpe.Mediator.Contracts |
Shared contracts for multi-project solutions |
See tests/Qorpe.Mediator.Sample.ECommerce/ for a complete e-commerce example with:
- Order aggregate root with domain events
- Commands with transactions, audit, authorization, idempotency, and retry
- Queries with caching and streaming
- FluentValidation validators
- Attribute-based HTTP endpoint mapping
- Full behavior pipeline configuration
See docs/MIGRATION_GUIDE.md for a step-by-step migration guide.
Contributions are welcome! Please open an issue or submit a pull request.