SH.Framework.Library.Cqrs.Implementation
1.0.1
dotnet add package SH.Framework.Library.Cqrs.Implementation --version 1.0.1
NuGet\Install-Package SH.Framework.Library.Cqrs.Implementation -Version 1.0.1
<PackageReference Include="SH.Framework.Library.Cqrs.Implementation" Version="1.0.1" />
<PackageVersion Include="SH.Framework.Library.Cqrs.Implementation" Version="1.0.1" />
<PackageReference Include="SH.Framework.Library.Cqrs.Implementation" />
paket add SH.Framework.Library.Cqrs.Implementation --version 1.0.1
#r "nuget: SH.Framework.Library.Cqrs.Implementation, 1.0.1"
#:package SH.Framework.Library.Cqrs.Implementation@1.0.1
#addin nuget:?package=SH.Framework.Library.Cqrs.Implementation&version=1.0.1
#tool nuget:?package=SH.Framework.Library.Cqrs.Implementation&version=1.0.1
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 that simplify implementation and promote consistent patterns across your application.
๐ Features
- ๐ Abstract Base Classes: Ready-to-use base classes for requests, handlers, and behaviors
- โ Result Pattern: Standardized Result<T> pattern for consistent error handling and response management
- ๐ Automatic ID Generation: Built-in request and notification ID generation
- ๐ก๏ธ Type Safety: Strongly-typed base classes that enforce proper CQRS patterns
- ๐ง Easy Implementation: Reduces boilerplate code and accelerates development
- ๐ Structured Error Handling: Comprehensive error tracking with codes, descriptions, and nested error details
- ๐ฏ Clean Architecture: Promotes separation of concerns and maintainable code structure
๐ฆ Installation
dotnet add package SH.Framework.Library.Cqrs.Implementation
Note: This package automatically includes
SH.Framework.Library.Cqrs
as a dependency.
๐ ๏ธ Quick Start
1. Register Services
using SH.Framework.Library.Cqrs;
var builder = WebApplication.CreateBuilder(args);
// Register CQRS library (this also registers the core framework)
builder.Services.AddCqrsLibraryConfiguration(
Assembly.GetExecutingAssembly()
);
var app = builder.Build();
2. Create Requests Using Base Classes
Command with Result Pattern:
using SH.Framework.Library.Cqrs.Implementation;
public class CreateUserRequest : Request<UserDto>
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
public record UserDto(int Id, string Name, string Email);
Command without Response Data:
public class DeleteUserRequest : Request
{
public int UserId { get; set; }
}
3. Implement Request Handlers
Handler with Response Data:
public class CreateUserRequestHandler : RequestHandler<CreateUserRequest, UserDto>
{
public override async Task<Result<UserDto>> HandleAsync(
CreateUserRequest request,
CancellationToken cancellationToken = default)
{
try
{
// Validate input
if (string.IsNullOrEmpty(request.Name))
{
return Result.Failure<UserDto>(
ResultCode.Failure,
new Dictionary<string, Dictionary<string, string>>
{
["Name"] = new() { ["Required"] = "Name is required" }
},
request.RequestId
);
}
// Business logic
var user = await CreateUserAsync(request.Name, request.Email);
var userDto = new UserDto(user.Id, user.Name, user.Email);
return Result.Success(userDto, request.RequestId);
}
catch (Exception ex)
{
return Result.Failure<UserDto>(
ResultCode.Exception,
new Dictionary<string, Dictionary<string, string>>
{
["Exception"] = new() { ["Message"] = ex.Message }
},
request.RequestId
);
}
}
}
Handler without Response Data:
public class DeleteUserRequestHandler : RequestHandler<DeleteUserRequest>
{
public override async Task<Result> HandleAsync(
DeleteUserRequest request,
CancellationToken cancellationToken = default)
{
try
{
await DeleteUserAsync(request.UserId);
return Result.Success(request.RequestId);
}
catch (Exception ex)
{
return Result.Failure(
ResultCode.Exception,
new Dictionary<string, Dictionary<string, string>>
{
["Exception"] = new() { ["Message"] = ex.Message }
},
request.RequestId
);
}
}
}
4. Create Notifications
public class UserCreatedNotification : Notification
{
public int UserId { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
5. Implement Notification Handlers
public class UserCreatedEmailNotificationHandler : NotificationBehavior<UserCreatedNotification>
{
public override async Task HandleAsync(
UserCreatedNotification notification,
CancellationToken cancellationToken = default)
{
// Send welcome email
await SendWelcomeEmailAsync(notification.Email);
}
}
6. Use in Controllers
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IProjector _projector;
public UsersController(IProjector projector)
{
_projector = projector;
}
[HttpPost]
public async Task<IActionResult> CreateUser(
CreateUserRequest request,
CancellationToken cancellationToken)
{
var result = await _projector.SendAsync(request, cancellationToken);
if (result.IsFailure)
{
return BadRequest(new
{
result.Code,
result.Description,
result.Errors,
result.RequestId
});
}
return Ok(new
{
result.Data,
result.RequestId
});
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(
int id,
CancellationToken cancellationToken)
{
var request = new DeleteUserRequest { UserId = id };
var result = await _projector.SendAsync(request, cancellationToken);
if (result.IsFailure)
{
return BadRequest(new
{
result.Code,
result.Description,
result.Errors,
result.RequestId
});
}
return NoContent();
}
}
๐๏ธ Architecture Components
Result Pattern
The Result pattern provides a standardized way to handle success and failure scenarios:
public class Result<TResponse> : Result
{
public virtual TResponse? Data { get; set; }
}
public class Result
{
public virtual int Code { get; init; }
public virtual string? Description { get; init; }
public virtual bool IsSuccess => Code == 0;
public virtual bool IsFailure => !IsSuccess;
public virtual Dictionary<string, Dictionary<string, string>>? Errors { get; init; }
public Guid? RequestId { get; init; }
}
Result Codes
Built-in result codes for common scenarios:
public static class ResultCode
{
public static ResultCode Success => Instance(0, "Success");
public static ResultCode Exception => Instance(1, "Exception");
public static ResultCode Failure => Instance(2, "Failure");
public static ResultCode Instance(int code, string? description) => new(code, description);
}
Base Classes
Request Base Classes:
// For requests that return data
public abstract class Request<TResponse> : IRequest<Result<TResponse>>, IHasRequestId
// For requests without return data
public abstract class Request : IRequest<Result>, IHasRequestId
Handler Base Classes:
// For handlers that return data
public abstract class RequestHandler<TRequest, TResponse> : IRequestHandler<TRequest, Result<TResponse>>
// For handlers without return data
public abstract class RequestHandler<TRequest> : IRequestHandler<TRequest, Result>
Notification Base Classes:
public abstract class Notification : INotification, IHasNotificationId
public abstract class NotificationBehavior<TNotification> : INotificationHandler<TNotification>
Behavior Base Classes:
public abstract class RequestBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IHasRequestId, IRequest<TResponse>
where TResponse : Result, new()
๐ง Advanced Usage
Custom Result Codes
public static class CustomResultCodes
{
public static ResultCode ValidationError => ResultCode.Instance(100, "Validation Error");
public static ResultCode NotFound => ResultCode.Instance(404, "Not Found");
public static ResultCode Unauthorized => ResultCode.Instance(401, "Unauthorized");
}
// Usage in handler
return Result.Failure<UserDto>(
CustomResultCodes.NotFound,
new Dictionary<string, Dictionary<string, string>>
{
["User"] = new() { ["NotFound"] = $"User with ID {request.UserId} not found" }
},
request.RequestId
);
Custom Pipeline Behavior
public class ValidationBehavior<TRequest, TResponse> : RequestBehavior<TRequest, TResponse>
where TRequest : IHasRequestId, IRequest<TResponse>
where TResponse : Result, new()
{
public override async Task<TResponse> HandleAsync(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken = default)
{
// Pre-processing validation logic
var validationResult = await ValidateRequestAsync(request);
if (validationResult.Any())
{
var result = new TResponse();
// Set validation errors
return result;
}
// Continue to next behavior/handler
var response = await next(cancellationToken);
// Post-processing logic
return response;
}
}
Error Handling Patterns
// Structured error handling
var errors = new Dictionary<string, Dictionary<string, string>>
{
["Email"] = new()
{
["Required"] = "Email is required",
["Format"] = "Email format is invalid"
},
["Password"] = new()
{
["MinLength"] = "Password must be at least 8 characters",
["Complexity"] = "Password must contain special characters"
}
};
return Result.Failure<UserDto>(ResultCode.Failure, errors, request.RequestId);
๐ Response Patterns
Success Response
{
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"code": 0,
"description": "Success",
"isSuccess": true,
"requestId": "123e4567-e89b-12d3-a456-426614174000"
}
Error Response
{
"code": 2,
"description": "Validation Error",
"isSuccess": false,
"errors": {
"Email": {
"Required": "Email is required",
"Format": "Email format is invalid"
}
},
"requestId": "123e4567-e89b-12d3-a456-426614174000"
}
๐ฏ Best Practices
- Always use the Result pattern for consistent error handling
- Include RequestId in all responses for tracking and debugging
- Structure your errors using the nested dictionary pattern
- Use meaningful result codes and descriptions
- Handle exceptions gracefully and return appropriate error results
- Leverage base classes to reduce boilerplate code
- Follow CQRS principles - separate commands from queries
๐ Dependencies
- SH.Framework.Library.Cqrs: Core CQRS framework (automatically included)
- .NET 9.0: Target framework
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ข Company
Strawhats Company
Created by Muharrem Kaรงkฤฑn
โญ If you find this library helpful, please consider giving it a star on GitHub!
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
- SH.Framework.Library.Cqrs (>= 1.4.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.0.1:
- Unneccasry file removed