296 lines
14 KiB
Markdown
296 lines
14 KiB
Markdown
> This project was originally initiated by [Powered Software Inc.](https://poweredsoft.com/) and was forked from the [PoweredSoft.CQRS](https://github.com/PoweredSoft/CQRS) Repository
|
|
|
|
# CQRS
|
|
|
|
Our implementation of query and command responsibility segregation (CQRS).
|
|
|
|
## Getting Started
|
|
|
|
> Install nuget package to your awesome project.
|
|
|
|
| Package Name | NuGet | NuGet Install |
|
|
|-----------------------------------------| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-----------------------------------------------------------------------:|
|
|
| Svrnty.CQRS | [](https://www.nuget.org/packages/Svrnty.CQRS/) | ```dotnet add package Svrnty.CQRS ``` |
|
|
| Svrnty.CQRS.MinimalApi | [](https://www.nuget.org/packages/Svrnty.CQRS.MinimalApi/) | ```dotnet add package Svrnty.CQRS.MinimalApi ``` |
|
|
| Svrnty.CQRS.AspNetCore | [](https://www.nuget.org/packages/Svrnty.CQRS.AspNetCore/) | ```dotnet add package Svrnty.CQRS.AspNetCore ``` |
|
|
| Svrnty.CQRS.FluentValidation | [](https://www.nuget.org/packages/Svrnty.CQRS.FluentValidation/) | ```dotnet add package Svrnty.CQRS.FluentValidation ``` |
|
|
| Svrnty.CQRS.DynamicQuery | [](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery/) | ```dotnet add package Svrnty.CQRS.DynamicQuery ``` |
|
|
| Svrnty.CQRS.DynamicQuery.AspNetCore | [](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery.AspNetCore/) | ```dotnet add package Svrnty.CQRS.DynamicQuery.AspNetCore ``` |
|
|
| Svrnty.CQRS.Grpc | [](https://www.nuget.org/packages/Svrnty.CQRS.Grpc/) | ```dotnet add package Svrnty.CQRS.Grpc ``` |
|
|
| Svrnty.CQRS.Grpc.Generators | [](https://www.nuget.org/packages/Svrnty.CQRS.Grpc.Generators/) | ```dotnet add package Svrnty.CQRS.Grpc.Generators ``` |
|
|
|
|
> Abstractions Packages.
|
|
|
|
| Package Name | NuGet | NuGet Install |
|
|
| ---------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -----------------------------------------------------: |
|
|
| Svrnty.CQRS.Abstractions | [](https://www.nuget.org/packages/Svrnty.CQRS.Abstractions/) | ```dotnet add package Svrnty.CQRS.Abstractions ``` |
|
|
| Svrnty.CQRS.AspNetCore.Abstractions | [](https://www.nuget.org/packages/Svrnty.CQRS.AspNetCore.Abstractions/) | ```dotnet add package Svrnty.CQRS.AspNetCore.Abstractions ``` |
|
|
| Svrnty.CQRS.DynamicQuery.Abstractions | [](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery.Abstractions/) | ```dotnet add package Svrnty.CQRS.DynamicQuery.Abstractions ``` |
|
|
| Svrnty.CQRS.Grpc.Abstractions | [](https://www.nuget.org/packages/Svrnty.CQRS.Grpc.Abstractions/) | ```dotnet add package Svrnty.CQRS.Grpc.Abstractions ``` |
|
|
|
|
|
|
## Sample of startup code for gRPC (Recommended)
|
|
|
|
```csharp
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Register CQRS core services
|
|
builder.Services.AddSvrntyCQRS();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
// Add your commands and queries
|
|
AddQueries(builder.Services);
|
|
AddCommands(builder.Services);
|
|
|
|
// Add gRPC support
|
|
builder.Services.AddGrpc();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Map auto-generated gRPC service implementations
|
|
app.MapGrpcService<CommandServiceImpl>();
|
|
app.MapGrpcService<QueryServiceImpl>();
|
|
|
|
// Enable gRPC reflection for tools like grpcurl
|
|
app.MapGrpcReflectionService();
|
|
|
|
app.Run();
|
|
```
|
|
|
|
### Important: gRPC Requirements
|
|
|
|
The gRPC implementation uses **Grpc.Tools** with `.proto` files and **source generators** for automatic service implementation:
|
|
|
|
#### 1. Install required packages:
|
|
|
|
```bash
|
|
dotnet add package Grpc.AspNetCore
|
|
dotnet add package Grpc.AspNetCore.Server.Reflection
|
|
dotnet add package Grpc.StatusProto # For Rich Error Model validation
|
|
```
|
|
|
|
#### 2. Add the source generator as an analyzer:
|
|
|
|
```bash
|
|
dotnet add package Svrnty.CQRS.Grpc.Generators
|
|
```
|
|
|
|
The source generator is automatically configured as an analyzer when installed via NuGet and will generate the gRPC service implementations at compile time.
|
|
|
|
#### 3. Define your proto files in `Protos/` directory:
|
|
|
|
```protobuf
|
|
syntax = "proto3";
|
|
import "google/protobuf/empty.proto";
|
|
|
|
service CommandService {
|
|
rpc AddUser(AddUserCommandRequest) returns (AddUserCommandResponse);
|
|
rpc RemoveUser(RemoveUserCommandRequest) returns (google.protobuf.Empty);
|
|
}
|
|
|
|
message AddUserCommandRequest {
|
|
string name = 1;
|
|
string email = 2;
|
|
int32 age = 3;
|
|
}
|
|
|
|
message AddUserCommandResponse {
|
|
int32 result = 1;
|
|
}
|
|
```
|
|
|
|
#### 4. Define your C# commands matching the proto structure:
|
|
|
|
```csharp
|
|
public record AddUserCommand
|
|
{
|
|
public required string Name { get; init; }
|
|
public required string Email { get; init; }
|
|
public int Age { get; init; }
|
|
}
|
|
|
|
public record RemoveUserCommand
|
|
{
|
|
public int UserId { get; init; }
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- The source generator automatically creates `CommandServiceImpl` and `QueryServiceImpl` implementations
|
|
- Property names in C# commands must match proto field names (case-insensitive)
|
|
- FluentValidation is automatically integrated with **Google Rich Error Model** for structured validation errors
|
|
- Validation errors return `google.rpc.Status` with `BadRequest` containing `FieldViolations`
|
|
- Use `record` types for commands/queries (immutable, value-based equality, more concise)
|
|
- No need for protobuf-net attributes
|
|
|
|
## Sample of startup code for Minimal API (Traditional HTTP)
|
|
|
|
For traditional HTTP/REST scenarios, you can use the Minimal API approach:
|
|
|
|
```csharp
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Register CQRS core services
|
|
builder.Services.AddSvrntyCQRS();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
// Add your commands and queries
|
|
AddQueries(builder.Services);
|
|
AddCommands(builder.Services);
|
|
|
|
// Add Swagger (optional)
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen();
|
|
|
|
var app = builder.Build();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
|
|
// Map CQRS endpoints - automatically creates routes for all commands and queries
|
|
app.MapSvrntyCommands(); // Creates POST /api/command/{commandName} endpoints
|
|
app.MapSvrntyQueries(); // Creates POST/GET /api/query/{queryName} endpoints
|
|
|
|
app.Run();
|
|
```
|
|
|
|
**Notes:**
|
|
- FluentValidation is automatically integrated with **RFC 7807 Problem Details** for structured validation errors
|
|
- Use `record` types for commands/queries (immutable, value-based equality, more concise)
|
|
- Supports both POST and GET (for queries) endpoints
|
|
- Automatically generates Swagger/OpenAPI documentation
|
|
|
|
## Sample enabling both gRPC and HTTP
|
|
|
|
You can enable both gRPC and traditional HTTP endpoints simultaneously, allowing clients to choose their preferred protocol:
|
|
|
|
```csharp
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Register CQRS core services
|
|
builder.Services.AddSvrntyCQRS();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
// Add your commands and queries
|
|
AddQueries(builder.Services);
|
|
AddCommands(builder.Services);
|
|
|
|
// Add gRPC support
|
|
builder.Services.AddGrpc();
|
|
|
|
// Add HTTP/REST support with Swagger
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen();
|
|
|
|
var app = builder.Build();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
|
|
// Map gRPC endpoints
|
|
app.MapGrpcService<CommandServiceImpl>();
|
|
app.MapGrpcService<QueryServiceImpl>();
|
|
app.MapGrpcReflectionService();
|
|
|
|
// Map HTTP/REST endpoints
|
|
app.MapSvrntyCommands();
|
|
app.MapSvrntyQueries();
|
|
|
|
app.Run();
|
|
```
|
|
|
|
**Benefits:**
|
|
- Single codebase supports multiple protocols
|
|
- gRPC for high-performance, low-latency scenarios (microservices, internal APIs)
|
|
- HTTP/REST for web browsers, legacy clients, and public APIs
|
|
- Same commands, queries, and validation logic for both protocols
|
|
- Swagger UI available for HTTP endpoints, gRPC reflection for gRPC clients
|
|
|
|
> Example how to add your queries and commands.
|
|
|
|
```csharp
|
|
private void AddCommands(IServiceCollection services)
|
|
{
|
|
services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler>();
|
|
services.AddTransient<IValidator<CreatePersonCommand>, CreatePersonCommandValidator>();
|
|
|
|
services.AddCommand<EchoCommand, string, EchoCommandHandler>();
|
|
services.AddTransient<IValidator<EchoCommand>, EchoCommandValidator>();
|
|
}
|
|
|
|
private void AddQueries(IServiceCollection services)
|
|
{
|
|
services.AddQuery<PersonQuery, IQueryable<Person>, PersonQueryHandler>();
|
|
}
|
|
```
|
|
|
|
# Fluent Validation
|
|
|
|
FluentValidation is optional but recommended for command and query validation. The `Svrnty.CQRS.FluentValidation` package provides extension methods to simplify validator registration.
|
|
|
|
## Without Svrnty.CQRS.FluentValidation
|
|
|
|
You need to register commands and validators separately:
|
|
|
|
```csharp
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using FluentValidation;
|
|
using Svrnty.CQRS;
|
|
|
|
private void AddCommands(IServiceCollection services)
|
|
{
|
|
// Register command handler
|
|
services.AddCommand<EchoCommand, string, EchoCommandHandler>();
|
|
|
|
// Manually register validator
|
|
services.AddTransient<IValidator<EchoCommand>, EchoCommandValidator>();
|
|
}
|
|
```
|
|
|
|
## With Svrnty.CQRS.FluentValidation (Recommended)
|
|
|
|
The package exposes extension method overloads that accept the validator as a generic parameter:
|
|
|
|
```bash
|
|
dotnet add package Svrnty.CQRS.FluentValidation
|
|
```
|
|
|
|
```csharp
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Svrnty.CQRS.FluentValidation; // Extension methods for validator registration
|
|
|
|
private void AddCommands(IServiceCollection services)
|
|
{
|
|
// Command without result - validator included in generics
|
|
services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
|
|
|
|
// Command with result - validator as last generic parameter
|
|
services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler, CreatePersonCommandValidator>();
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- **Single line registration** - Handler and validator registered together
|
|
- **Type safety** - Compiler ensures validator matches command type
|
|
- **Less boilerplate** - No need for separate `AddTransient<IValidator<T>>()` calls
|
|
- **Cleaner code** - Clear intent that validation is part of command pipeline
|
|
|
|
# 2024-2025 Roadmap
|
|
|
|
| Task | Description | Status |
|
|
|----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|--------|
|
|
| Support .NET 8 | Ensure compatibility with .NET 8. | ✅ |
|
|
| Support .NET 10 | Upgrade to .NET 10 with C# 14 language support. | ✅ |
|
|
| Update FluentValidation | Upgrade FluentValidation to version 11.x for .NET 10 compatibility. | ✅ |
|
|
| Add gRPC Support with source generators | Implement gRPC endpoints with source generators and Google Rich Error Model for validation. | ✅ |
|
|
| Create a demo project (Svrnty.CQRS.Grpc.Sample) | Develop a comprehensive demo project showcasing gRPC and HTTP endpoints. | ✅ |
|
|
| Create a website for the Framework | Develop a website to host comprehensive documentation for the framework. | ⬜️ | |