DotnetCqrsPgTemplate.Api
1.0.5
dotnet add package DotnetCqrsPgTemplate.Api --version 1.0.5
NuGet\Install-Package DotnetCqrsPgTemplate.Api -Version 1.0.5
<PackageReference Include="DotnetCqrsPgTemplate.Api" Version="1.0.5" />
<PackageVersion Include="DotnetCqrsPgTemplate.Api" Version="1.0.5" />
<PackageReference Include="DotnetCqrsPgTemplate.Api" />
paket add DotnetCqrsPgTemplate.Api --version 1.0.5
#r "nuget: DotnetCqrsPgTemplate.Api, 1.0.5"
#:package DotnetCqrsPgTemplate.Api@1.0.5
#addin nuget:?package=DotnetCqrsPgTemplate.Api&version=1.0.5
#tool nuget:?package=DotnetCqrsPgTemplate.Api&version=1.0.5
Command Query Responsibility Segregation (CQRS) Pattern with Postgres Database .Net API Template
DotnetCqrsPgTemplate is a comprehensive, ready-to-use template for building modern .NET APIs with Postgres Database in CQRS pattern. Designed to simplify and accelerate API development, this template integrates essential tools and follows best practices, making it ideal for developers looking for a solid foundation for their projects.
Getting Started
Features
- Includes Postgres support
- Supports Swagger documentation
- Includes API versioning
- Includes token-based authentication and RBAC-PBAC support
- Automatically resolves XML comments from project files
- Includes annotations for better documentation
- Includes a sample controller and handlers
- Includes a sample model
Requirements
- .NET 8.0
- Compatible with ASP.NET Core applications
- Postgres server
Installation
To install the api project Template globally on your machine via NuGet:
- Open a terminal or command prompt.
- Run the following command:
dotnet new install DotnetCqrsPgTemplate.Api
Usage
To create a new api project using the template:
- Open a terminal or command prompt.
- Run the following command:
dotnet new dotnet-cqrs-pg-api-v8 -n MyApi
Replace MyApi
with your desired project name.
Project Structure and Use Cases
IRequestHandler<TRequest, TResponse>
The IRequestHandler<TRequest, TResponse> interface is a core component in the CQRS (Command Query Responsibility Segregation) pattern.
It defines a contract for handling requests (commands or queries) and returning a response. This pattern helps to separate the logic for processing different types of requests, improving maintainability and testability.
To use the IRequestHandler<TRequest,TResponse> interface, your request needs to inherit from either: IQuery<TResponse>
for queries or ICommand<TResponse>
for commands. or you can use the IRequest<TResponse>
interface for both commands and queries.
public class AddUserRequest:ICommand<ApiResponse<UserResponse>>
{
public string Name { get; set; }
public string Email { get; set; }
}
public class AddUserRequestHandler(IUserRepository userRepository, ILogger<AddUserRequestHandler>logger): IRequestHandler<AddUserRequest, ApiResponse<UserResponse>>
{
public async Task<ApiResponse<UserResponse>> Handle(AddUserRequest request, CancellationToken cancellationToken)
{
var user = new User
{
Name = request.Name,
Email = request.Email
};
await userRepository.AddAsync(user, cancellationToken);
return CommonResponse.CreatedResponse(user.Adapt<UserResponse>(), "User created successfully");
}
}
IValidator<TRequest>
The IValidator<TRequest> interface is designed to encapsulate validation logic for request objects in a CQRS or API-based application.
It allows you to define and enforce business rules and data integrity checks before a request is processed by the application. The TRequest
of the IValidator<>
interface inherits from either: IRequest<>
, ICommand<>
, or IQuery<>
interfaces.
Example:
public class AddUserRequestValidation(IUserRepository userRepository):IValidator<AddUserRequest>
{
public async Task<IEnumerable<ErrorResponse>> ValidateAsync(EditUserRequest request, CancellationToken cancellationToken)
{
var errors = new List<ErrorResponse>();
var emailExist = await userRepository.GetQueryable()
.AnyAsync(item => string.Equals(request.Email, item.Email),cancellationToken);
if (emailExist)
{
errors.Add(new ErrorResponse(nameof(request.Email), "Email already exists"));
}
return errors;
}
}
The AddUserRequestHandler
will only be executed if the AddUserRequestValidation
passes without errors.
IAuditableCommandHandler<TRequest, TResponse>
The IAuditableCommandHandler<TRequest, TResponse>
interface is designed to provide hooks for auditing command operations in a CQRS-based application. It allows you to execute custom logic before and after a command is handled, typically for logging, compliance, or traceability purposes.
To use this interface, implement it in a class that handles a specific command. For example, the AddEmployeeAuditHandler implements auditing for the AddEmployeeRequest command:
public class AddEmployeeAuditHandler : IAuditableCommandHandler<AddEmployeeRequest, ApiResponse<Employee>>
{
public async Task PreAuditAsync(AddEmployeeRequest request, CancellationToken cancellationToken)
{
// Logic to execute before the command is handled (e.g., logging the request)
}
public async Task PostAuditAsync(AddEmployeeRequest request, ApiResponse<Employee> response, CancellationToken cancellationToken)
{
// Logic to execute after the command is handled (e.g., logging the request and response)
}
}
ICorrelationHandler
The ICorrelationHandler
interface is designed to provide hooks for tracing and correlating requests and responses throughout the lifecycle of an operation in a CQRS-based or distributed application.
It enables consistent tracking of requests, responses, and their associated metadata, which is essential for auditing, debugging, and monitoring.
To use this interface, implement it in a class that will handle the tracing or correlation logic. For example,
the ApplicationTraceHandler
class implements ICorrelationHandler
to log and persist trace information for each request:
public class ApplicationTraceHandler : ICorrelationHandler
{
public async Task OnStartAsync(string traceId, object request, CancellationToken cancellationToken)
{
// Logic to execute at the start of a request (e.g., logging, persisting trace info)
}
public async Task OnCompleteAsync(string traceId, object request, object response, long durationMs, CancellationToken cancellationToken)
{
// Logic to execute at the completion of a request (e.g., logging, updating trace info)
}
}
Benefits
- Observability: Enables end-to-end tracing of requests for monitoring and diagnostics.
- Auditing: Captures detailed information about each request and response for compliance and analysis.
- Extensibility: Can be customized to include additional metadata or integrate with external tracing systems.
IRequestPreProcessor<TRequest>
The IRequestPreProcessor<TRequest>
interface is used to define logic that should be executed before a request (such as a command or query) is handled in a CQRS-based application.
This is useful for implementing cross-cutting concerns like logging, validation, authorization, or enrichment of the request.
To use this interface, implement it for a specific request type. The Process
method will be called before the main request handler executes.
public class UserOnLoginRequestHandler : IRequestPreProcessor<LoginRequest>
{
public async Task Process(LoginRequest request, CancellationToken cancellationToken)
{
// Pre-processing logic, such as logging or validation
}
}
Benefits
- Separation of Concerns: Keeps pre-processing logic separate from business logic.
- Reusability: Can be reused for different request types.
- Extensibility: Easily add more pre-processing steps as needed.
Typical Use Cases
- Logging incoming requests
- Validating or enriching requests
- Checking permissions or authorization
- Setting up context or correlation IDs
IRequestPostProcessor<TRequest, TResponse>
The IRequestPostProcessor<TRequest, TResponse>
interface is used to define logic that should be executed after a request (such as a command or query) has been handled in a CQRS-based application.
This is useful for implementing cross-cutting concerns like logging, auditing, notifications, or post-processing actions.
To use this interface, implement it for a specific request and response type. The Process method will be called after the main request handler has executed and produced a response.
public class UserLoggedInRequestHandler : IRequestPostProcessor<LoginRequest, ApiResponse<AuthResponse>>
{
public async Task Process(LoginRequest request, ApiResponse<AuthResponse> response, CancellationToken cancellationToken)
{
// Post-processing logic, such as logging or updating user statistics
}
}
Benefits
- Separation of Concerns: Keeps post-processing logic separate from business logic.
- Reusability: Can be reused for different request/response types.
- Extensibility: Easily add more post-processing steps as needed.
Typical Use Cases
- Logging responses or important events
- Auditing actions and outcomes
- Sending notifications or emails
- Updating statistics or triggering workflows
ILoggingHook<TRequest, TResponse>
The ILoggingHook<TRequest, TResponse>
interface is designed to provide hooks for logging the lifecycle events of a request in a CQRS-based application.
It enables you to log when a request starts processing, completes, or encounters an exception, supporting observability and troubleshooting.
To use this interface, implement it for a specific request and response type. The methods will be called at different stages of the request lifecycle:
OnLogStartAsync
: Invoked before the request is processed.OnLogCompleteAsync
: Invoked after the request is successfully processed.OnLogExceptionAsync
: Invoked if an exception occurs during processing.
public class UserLoginLoggingHook : ILoggingHook<LoginRequest, ApiResponse<AuthResponse>>
{
public async Task OnLogStartAsync(LoginRequest request, CancellationToken cancellationToken)
{
// Logic to log the start of processing the request
}
public async Task OnLogCompleteAsync(LoginRequest request, ApiResponse<AuthResponse> response, CancellationToken cancellationToken)
{
// Logic to log the successful completion of the request
}
public async Task OnLogExceptionAsync(LoginRequest request, Exception exception, CancellationToken cancellationToken)
{
// Logic to log any exceptions that occur during processing
}
}
Benefits
- Observability: Provides detailed logs for request processing, aiding in monitoring and debugging.
- Separation of Concerns: Keeps logging logic separate from business logic.
- Extensibility: Can be implemented for any request/response type as needed.
Typical Use Cases
- Logging request and response data for auditing or diagnostics
- Tracking the timing and status of request processing
- Capturing and logging exceptions for error analysis.
This approach ensures consistent and maintainable logging for all request lifecycles in your application.
IAuthorizedRequest
The IAuthorizedRequest
interface is used to define authorization requirements for a request in a CQRS-based application.
It allows you to specify which user roles and permissions are required to execute a particular request, enabling fine-grained access control.
This interface provides a swift implementation of Role-Based Access Control (RBAC
) and Policy-Based Access Control (PBAC
) in your application.
To use this interface, implement it in your request class and specify the required roles and permissions. For example:
public class AddUserRequest : ICommand<ApiResponse<UserResponse>>, IAuthorizedRequest
{
public string Name { get; set; }
public string Email { get; set; }
public string[] RequiredRoles => [UserRole.DefaultSuperAdmin];
public string[] RequiredPermissions => [Permissions.DefaultCreate];
}
The permissions of the authenticated user will be extracted from the JWT token in the ClaimType
of "permission" and checked against the RequiredPermissions
of the request.<br>
The roles of the authenticated user will be extracted from the JWT token in the ClaimType
of "role" and checked against the RequiredRoles
of the request.
Benefits
- Security: Ensures only authorized users can execute sensitive operations.
- Separation of Concerns: Keeps authorization logic declarative and separate from business logic.
- Extensibility: Easily add or modify roles and permissions for different requests.
Typical Use Cases
- Restricting access to administrative actions
- Enforcing permission checks for create, update, or delete operations
- Associating user context with requests for auditing or logging
INotificationHandler<TRequest>
The INotificationHandler<TRequest>
interface is used to handle notification events in a CQRS or MediatR-based application.
Unlike request handlers, notification handlers are designed for one-way messages that do not expect a response. They are ideal for implementing event-driven or publish/subscribe patterns, where multiple handlers can react to the same event.
To use this interface, implement it for a specific notification type. The Handle
method will be called whenever the notification is published.
Your TRequest
type should implement the INotification
interface, which indicates that it is a notification rather than a request that expects a response.
public class UserCreatedNotification : INotification
{
public UserResponse User { get; set; }
}
public class UserCreatedNotificationHandler : INotificationHandler<UserCreatedNotification>
{
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
// Logic to handle the notification, such as sending an email or logging
}
}
To publish the notification, You will call the Publish
method of the IMediator
interface, which is typically injected into your service
or controller
:
Benefits
- Event-Driven Architecture: Supports publish/subscribe patterns, allowing multiple handlers to respond to the same event.
- Separation of Concerns: Keeps event handling logic separate from business logic.
- Scalability: Easily add new handlers for the same notification without modifying existing code.
Typical Use Cases
- Logging or auditing events
- Sending notifications or emails
- Updating statistics or triggering workflows
- Integrating with external systems
Using INotificationHandler<TRequest>
with DomainEventBase
and EventEntity
for Domain Driven Design
In a Domain Driven Design (DDD) context, you can use INotificationHandler<TRequest>
in conjunction with DomainEventBase
and EventEntity
to handle domain events effectively. This allows you to decouple your domain logic from the event handling logic, promoting a clean architecture.
Example: Given that you want to send notification to a user when a new login attempt is made on their account.
// The request that will trigger the domain event must inherit from `EventEntity`:
public class LoginRequest :EventEntity, ICommand<ApiResponse<EmptyDto>>
{
public string Email { get; set; }
public string Password { get; set; }
//Declaration of the `OnLoggedIn` method to handle the login event:
public void OnLoggedIn(UserResponse user)
{
AddDomainEvent(new UserLoggedInEventRequest
{
User = user,
});
}
// The OccurredOn property is inherited from the EventEntity class
public DateTime OccurredOn { get; }= DateTime.UtcNow;
}
// The INotificationHandler request type should implement the `DomainEventBase` class:
public class UserLoggedInEventRequest:DomainEventBase
{
public UserResponse?User { get; set; }
}
//LoginRequestHandler will handle the login request and trigger the domain event:
public class UserLoggedInEventHandler : IRequestHandler<LoginRequest, ApiResponse<EmptyDto>>
{
public async Task Handle(LoginRequest request, CancellationToken cancellationToken)
{
//The login request handler will perform it traditional login logic.
//for the context of this example, we will assume that the user is authenticated successfully.
// After successful login, trigger the domain event
request.OnLoggedIn(new UserResponse
{
//.... details of the user
});
}
}
// The INotificationHandler will handle the domain event:
public class UserLoggedInNotificationHandler : INotificationHandler<UserLoggedInEventRequest>
{
public async Task Handle(UserLoggedInEventRequest notification, CancellationToken cancellationToken)
{
// Logic to handle the user logged in event, such as sending a notification
var user = notification.User;
if (user != null)
{
// Send notification to the user
Console.WriteLine($"User {user.Name} logged in at {notification.OccurredOn}");
}
}
}
With this setup, when the LoginRequest
is processed, it will trigger the UserLoggedInEventRequest
domain event, which will be handled by the UserLoggedInNotificationHandler
. This allows you to keep your domain logic clean and focused on business rules while handling events separately.
Benefits
- Decoupling: Keeps domain logic separate from event handling, promoting a clean architecture.
- Scalability: Easily add new event handlers without modifying existing code.
- Flexibility: Allows for complex event handling scenarios, such as chaining multiple handlers or integrating with external systems.
- Observability: Provides a clear audit trail of domain events and their handling.
- Extensibility: Easily extend the domain event system to include additional metadata or custom logic as needed.
- Testability: Simplifies unit testing by allowing you to mock or stub event handlers independently of the domain logic.
IStreamRequestHandler<TRequest, TResponse>
The IStreamRequestHandler<TRequest, TResponse>
interface is designed for handling streaming requests in a CQRS or MediatR-based application.
It enables the implementation of handlers that return a sequence of responses asynchronously,
which is useful for scenarios where data needs to be processed or delivered in chunks or as a live stream.
To use this interface, implement it for a specific request and response type. The Handle method should return an IAsyncEnumerable<TResponse>, allowing consumers to asynchronously iterate over the results as they become available. Example:
public class StreamUserRequest : IStreamRequest<UserResponse>
{
public int Page { get; set; }
public int PageSize { get; set; }
}
public class StreamUserRequestHandler : IStreamRequestHandler<StreamUserRequest, UserResponse>
{
private readonly IUserRepository _userRepository;
public StreamUserRequestHandler(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async IAsyncEnumerable<UserResponse> Handle(StreamUserRequest request)
{
var users =await _userRepository.GetUsers(request.Page, request.PageSize);
foreach (var user in users)
{
yield return user.Adapt<UserResponse>();// Simulate delay for streaming
}
}
}
Implementing Retry using IResilientRequest
: for Intentional Retry Pattern
The IResilientRequest interface is designed to enable intentional retry logic for requests in your CQRS-based application. This pattern is useful for handling transient failures, such as network issues or temporary unavailability of external services, by automatically retrying the operation according to configurable parameters. The interface provides properties to define the maximum number of retries and the delay between retries. To implement retry logic using IResilientRequest, you can create a request class that implements the interface and specify the retry parameters. If and exception eg. TimeoutException occurs during the request handling, the system will automatically retry the operation based on the defined parameters. Your request handler will be called multiple times until it succeeds or the maximum retry count is reached.
//Example. Verifying Employee KYC details with retry logic
public class VerifyEmployeeKycRequest : ICommand<ApiResponse<EmptyDto>>, IResilientRequest
{
public string? EmployeeId { get; set; }
//other properties...
// Retry parameters
public int RetryCount { get; } = 3;//Number of retry attempts
public int RetryDelayMilliseconds { get; }= 1000; //Time interval between retry attempts
public int TimeoutSeconds { get; }= 30;
}
// The request handler that implements the kyc verification logic
public class VerifyEmployeeKycRequestHandler : IRequestHandler<VerifyEmployeeKycRequest, ApiResponse<EmptyDto>>
{
private readonly IKycService _kycService;
public VerifyEmployeeKycRequestHandler(IKycService kycService)
{
_kycService = kycService;
}
public async Task<ApiResponse<EmptyDto>> Handle(VerifyEmployeeKycRequest request, CancellationToken cancellationToken)
{
try
{
// Call the KYC service to verify employee details
await _kycService.VerifyAsync(request.EmployeeId, cancellationToken);
return CommonResponse.OkResponse(new EmptyDto(), "KYC verification successful");
}
catch (TimeoutException ex)
{
// Handle timeout exception and allow retry logic to kick in
throw new TimeoutException("KYC verification timed out", ex);
}
}
}
Bear in mind that the retry logic is applied at the request level, meaning that if the request fails due to a transient error, it will be retried according to the specified parameters.
And the retry logic is automatically handled by the ScopeBus
pipeline, so you don't need to implement it manually in your request handler.
Also if your request doesn't implement the IResilientRequest
interface, it will not be retried, and any exceptions will be propagated immediately.
Conclusion
This template provides a solid foundation for building modern .NET APIs using the CQRS pattern with Postgres Database. By leveraging the features and best practices outlined in this documentation, you can accelerate your API development process while ensuring maintainability, scalability, and security.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- AkkaNetApiAdapter (>= 1.1.42)
- JwtAdapter (>= 1.1.15)
- KafkaProducerHost (>= 1.0.0)
- Mapster (>= 7.0.0)
- Microsoft.AspNetCore.Mvc.Versioning (>= 5.1.0)
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer (>= 5.1.0)
- Microsoft.EntityFrameworkCore (>= 8.0.6)
- Microsoft.EntityFrameworkCore.UnitOfWork (>= 3.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
- Newtonsoft.Json (>= 13.0.3)
- Npgsql (>= 8.0.3)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 8.0.4)
- Npgsql.Json.NET (>= 8.0.3)
- ScopeBus (>= 1.1.1)
- Swashbuckle.AspNetCore (>= 6.6.2)
- Swashbuckle.AspNetCore.Annotations (>= 6.6.2)
- Swashbuckle.AspNetCore.ReDoc (>= 6.6.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.