SH.Framework.Library.Cqrs
1.2.0
See the version list below for details.
dotnet add package SH.Framework.Library.Cqrs --version 1.2.0
NuGet\Install-Package SH.Framework.Library.Cqrs -Version 1.2.0
<PackageReference Include="SH.Framework.Library.Cqrs" Version="1.2.0" />
<PackageVersion Include="SH.Framework.Library.Cqrs" Version="1.2.0" />
<PackageReference Include="SH.Framework.Library.Cqrs" />
paket add SH.Framework.Library.Cqrs --version 1.2.0
#r "nuget: SH.Framework.Library.Cqrs, 1.2.0"
#:package SH.Framework.Library.Cqrs@1.2.0
#addin nuget:?package=SH.Framework.Library.Cqrs&version=1.2.0
#tool nuget:?package=SH.Framework.Library.Cqrs&version=1.2.0
SH.Framework.Library.Cqrs
A lightweight and high-performance library implementing the Command Query Responsibility Segregation (CQRS) pattern for .NET 9.0. Provides clean architecture and separated responsibilities in modern .NET applications with support for Request/Response, Notifications, and Pipeline Behaviors.
Version 1.1.0 Highlights
- Enhanced Error Handling: Custom exceptions for better debugging and error management
- Performance Optimizations: Reflection caching for improved throughput
- Robust Validation: Null argument validation and improved exception messages
- Better Logging: Enhanced ILogger integration with dependency injection
- Improved Equality: Better Unit struct with proper equality semantics
Features
- Request/Response Pattern: Handle commands and queries with typed responses
- Notification System: Publish and handle domain events asynchronously
- Pipeline Behaviors: Add cross-cutting concerns like validation, logging, and caching
- Dependency Injection Integration: Seamless integration with Microsoft.Extensions.DependencyInjection
- High Performance: Optimized for minimal overhead and maximum throughput with reflection caching
- Clean Architecture: Promotes separation of concerns and maintainable code
- Robust Error Handling: Custom exceptions for better error management
- Comprehensive Logging: Built-in logging support with configurable error handling
Installation
dotnet add package SH.Framework.Library.Cqrs
Quick Start
1. Register the Library
using SH.Framework.Library.Cqrs;
var builder = WebApplication.CreateBuilder(args);
// Register CQRS library with assemblies containing handlers
builder.Services.AddCqrsLibraryConfiguration(
Assembly.GetExecutingAssembly(),
typeof(MyHandler).Assembly
);
var app = builder.Build();
2. Create a Command/Query
// Command (no return value)
public record CreateUserCommand(string Name, string Email) : IRequest;
// Query (with return value)
public record GetUserQuery(int Id) : IRequest<UserDto>;
// Response DTO
public record UserDto(int Id, string Name, string Email);
3. Create Handlers
// Command Handler
public class CreateUserHandler : IRequestHandler<CreateUserCommand>
{
private readonly IUserRepository _repository;
public CreateUserHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<Unit> HandleAsync(CreateUserCommand request,
CancellationToken cancellationToken = default)
{
var user = new User(request.Name, request.Email);
await _repository.SaveAsync(user);
return Unit.Value;
}
}
// Query Handler
public class GetUserHandler : IRequestHandler<GetUserQuery, UserDto>
{
private readonly IUserRepository _repository;
public GetUserHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<UserDto> HandleAsync(GetUserQuery request,
CancellationToken cancellationToken = default)
{
var user = await _repository.GetByIdAsync(request.Id);
return new UserDto(user.Id, user.Name, user.Email);
}
}
4. Use the Projector
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IProjector _projector;
public UsersController(IProjector projector)
{
_projector = projector;
}
[HttpPost]
public async Task<IActionResult> CreateUser(CreateUserCommand command)
{
await _projector.SendAsync(command);
return Ok();
}
[HttpGet("{id}")]
public async Task<UserDto> GetUser(int id)
{
return await _projector.SendAsync(new GetUserQuery(id));
}
}
Notifications (Domain Events)
1. Create a Notification
public record UserCreatedNotification(int UserId, string Name, string Email) : INotification;
2. Create Notification Handlers
// Email notification handler
public class SendWelcomeEmailHandler : INotificationHandler<UserCreatedNotification>
{
private readonly IEmailService _emailService;
public SendWelcomeEmailHandler(IEmailService emailService)
{
_emailService = emailService;
}
public async Task HandleAsync(UserCreatedNotification notification,
CancellationToken cancellationToken = default)
{
await _emailService.SendWelcomeEmailAsync(notification.Email, notification.Name);
}
}
// Audit log handler
public class LogUserCreationHandler : INotificationHandler<UserCreatedNotification>
{
private readonly ILogger<LogUserCreationHandler> _logger;
public LogUserCreationHandler(ILogger<LogUserCreationHandler> logger)
{
_logger = logger;
}
public async Task HandleAsync(UserCreatedNotification notification,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("User created: {UserId} - {Name}",
notification.UserId, notification.Name);
}
}
3. Publish Notifications
public class CreateUserHandler : IRequestHandler<CreateUserCommand>
{
private readonly IUserRepository _repository;
private readonly IProjector _projector;
public CreateUserHandler(IUserRepository repository, IProjector projector)
{
_repository = repository;
_projector = projector;
}
public async Task<Unit> HandleAsync(CreateUserCommand request,
CancellationToken cancellationToken = default)
{
var user = new User(request.Name, request.Email);
await _repository.SaveAsync(user);
// Publish notification
var notification = new UserCreatedNotification(user.Id, user.Name, user.Email);
await _projector.PublishAsync(notification, cancellationToken);
return Unit.Value;
}
}
Pipeline Behaviors
Pipeline behaviors allow you to add cross-cutting concerns like validation, logging, caching, etc.
1. Create a Validation Behavior
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest>? _validator;
public ValidationBehavior(IValidator<TRequest>? validator = null)
{
_validator = validator;
}
public async Task<TResponse> HandleAsync(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken = default)
{
if (_validator != null)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors
.GroupBy(x => x.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(x => x.ErrorMessage).ToArray());
throw new CqrsValidationException(errors);
}
}
return await next(cancellationToken);
}
}
2. Create a Logging Behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> HandleAsync(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken = default)
{
var requestName = typeof(TRequest).Name;
_logger.LogInformation("Handling {RequestName}", requestName);
var stopwatch = Stopwatch.StartNew();
var response = await next(cancellationToken);
stopwatch.Stop();
_logger.LogInformation("Handled {RequestName} in {ElapsedMs}ms",
requestName, stopwatch.ElapsedMilliseconds);
return response;
}
}
3. Register Behaviors
Note: You need to manually register pipeline behaviors in your DI container:
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
Notification Behaviors
Similar to pipeline behaviors, but for notifications:
public class NotificationLoggingBehavior<TNotification> : INotificationBehavior<TNotification>
where TNotification : INotification
{
private readonly ILogger<NotificationLoggingBehavior<TNotification>> _logger;
public NotificationLoggingBehavior(ILogger<NotificationLoggingBehavior<TNotification>> logger)
{
_logger = logger;
}
public async Task HandleAsync(TNotification notification,
NotificationHandlerDelegate next,
CancellationToken cancellationToken = default)
{
var notificationName = typeof(TNotification).Name;
_logger.LogInformation("Publishing {NotificationName}", notificationName);
await next(cancellationToken);
_logger.LogInformation("Published {NotificationName}", notificationName);
}
}
Request and Notification IDs
The library provides interfaces for tracking requests and notifications:
public record CreateUserCommand(string Name, string Email) : IRequest, IHasRequestId
{
public Guid RequestId { get; init; } = Guid.NewGuid();
}
public record UserCreatedNotification(int UserId, string Name, string Email) : INotification, IHasNotificationId
{
public Guid NotificationId { get; init; } = Guid.NewGuid();
}
Error Handling
Built-in Exceptions
The library includes custom exceptions for better error handling:
CqrsValidationException
: Thrown when validation fails, contains detailed error informationHandlerNotFoundException
: Thrown when no handler is found for a request typeMultipleHandlersFoundException
: Thrown when multiple handlers are found for a single request
Exception Usage Examples
try
{
var result = await _projector.SendAsync(new GetUserQuery(invalidId));
}
catch (HandlerNotFoundException ex)
{
_logger.LogError("No handler found for request type: {RequestType}", ex.RequestType.Name);
return NotFound();
}
catch (CqrsValidationException ex)
{
_logger.LogWarning("Validation failed with {ErrorCount} errors", ex.Errors.Count);
return BadRequest(ex.Errors);
}
Notification Error Handling
Notification handlers include built-in error handling with logging support. If one handler fails, others will still execute:
// The Projector automatically handles notification handler failures
// Errors are logged but don't prevent other handlers from executing
await _projector.PublishAsync(notification); // All handlers will attempt to run
Performance Optimizations
Version 1.1.0 includes significant performance improvements:
- Reflection Caching: Handler and behavior method lookups are cached using
ConcurrentDictionary
- Type Caching: Generic type construction is cached to avoid repeated reflection operations
- Optimized Handler Resolution: Faster service resolution with cached type information
Best Practices
- Keep Handlers Simple: Each handler should have a single responsibility
- Use Notifications for Side Effects: Commands should focus on the main operation, use notifications for side effects
- Validate Early: Use pipeline behaviors for validation before reaching handlers
- Log Appropriately: Use logging behaviors to track request execution
- Handle Errors Gracefully: Implement proper error handling in your handlers using the provided exception types
- Use Cancellation Tokens: Always pass and respect cancellation tokens
- Leverage Caching: The library's built-in caching optimizes performance automatically
Performance Considerations
- The library is designed for high performance with minimal allocations and reflection caching
- Behaviors are executed in reverse order of registration (LIFO)
- Notification handlers execute in parallel when possible with built-in error isolation
- Use appropriate DI lifetimes (typically Scoped for web applications)
- Handler and behavior method resolution is cached for optimal performance
Logging Integration
The library integrates seamlessly with the Microsoft.Extensions.Logging framework:
// Register logging in your DI container
builder.Services.AddLogging();
// The Projector will automatically use ILogger<Projector> if available
// Notification handler failures are automatically logged with structured information
Requirements
- .NET 9.0 or later
- Microsoft.Extensions.DependencyInjection 9.0.8 or later
- Microsoft.Extensions.Logging.Abstractions 9.0.8 or later
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests on GitHub.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Repository
https://github.com/muharremkackin/sh-framework-cqrs
Changelog
v1.1.0
- Fixed behavior registration in AddCqrsLibraryConfiguration
- Improved error handling with custom exceptions (
CqrsValidationException
,HandlerNotFoundException
,MultipleHandlersFoundException
) - Added reflection caching for better performance
- Enhanced Unit struct with proper equality semantics
- Better logging integration with ILogger dependency injection
- Added null argument validation
- Improved exception messages and error handling
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.Extensions.DependencyInjection (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SH.Framework.Library.Cqrs:
Package | Downloads |
---|---|
SH.Framework.Library.Cqrs.Implementation
A comprehensive implementation layer for the SH.Framework.Library.Cqrs package, providing abstract base classes and Result pattern implementation for CQRS operations. This package extends the core CQRS framework with practical base classes for requests, handlers, behaviors, and a standardized Result pattern for better error handling and response management. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.2.0:
- Enhanced reflection caching with ConcurrentDictionary for better performance
- Improved error handling with comprehensive custom exceptions
- Added null argument validation throughout the library
- Better logging integration with structured logging support
- Enhanced Unit struct with proper equality semantics and operators
- Optimized behavior execution pipeline with reverse order processing
- Added comprehensive XML documentation
- Improved exception messages with detailed context information
- Enhanced parallel notification handler execution with error isolation
- Added support for source link and symbol packages
- Performance improvements in handler resolution and caching
- Better integration with Microsoft.Extensions.Logging.Abstractions
- Comprehensive testing and validation improvements