> 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 | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS/) | ```dotnet add package Svrnty.CQRS ``` | | Svrnty.CQRS.MinimalApi | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.MinimalApi.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.MinimalApi/) | ```dotnet add package Svrnty.CQRS.MinimalApi ``` | | Svrnty.CQRS.AspNetCore | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.AspNetCore.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.AspNetCore/) | ```dotnet add package Svrnty.CQRS.AspNetCore ``` | | Svrnty.CQRS.FluentValidation | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.FluentValidation.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.FluentValidation/) | ```dotnet add package Svrnty.CQRS.FluentValidation ``` | | Svrnty.CQRS.DynamicQuery | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.DynamicQuery.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery/) | ```dotnet add package Svrnty.CQRS.DynamicQuery ``` | | Svrnty.CQRS.DynamicQuery.AspNetCore | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.DynamicQuery.AspNetCore.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery.AspNetCore/) | ```dotnet add package Svrnty.CQRS.DynamicQuery.AspNetCore ``` | | Svrnty.CQRS.Grpc | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.Grpc.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.Grpc/) | ```dotnet add package Svrnty.CQRS.Grpc ``` | | Svrnty.CQRS.Grpc.Generators | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.Grpc.Generators.svg?style=flat-square&label=nuget)](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 | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.Abstractions.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.Abstractions/) | ```dotnet add package Svrnty.CQRS.Abstractions ``` | | Svrnty.CQRS.AspNetCore.Abstractions | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.AspNetCore.Abstractions.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.AspNetCore.Abstractions/) | ```dotnet add package Svrnty.CQRS.AspNetCore.Abstractions ``` | | Svrnty.CQRS.DynamicQuery.Abstractions | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.DynamicQuery.Abstractions.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Svrnty.CQRS.DynamicQuery.Abstractions/) | ```dotnet add package Svrnty.CQRS.DynamicQuery.Abstractions ``` | | Svrnty.CQRS.Grpc.Abstractions | [![NuGet](https://img.shields.io/nuget/v/Svrnty.CQRS.Grpc.Abstractions.svg?style=flat-square&label=nuget)](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(); app.MapGrpcService(); // 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(); app.MapGrpcService(); 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(); services.AddTransient, CreatePersonCommandValidator>(); services.AddCommand(); services.AddTransient, EchoCommandValidator>(); } private void AddQueries(IServiceCollection services) { services.AddQuery, 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(); // Manually register validator services.AddTransient, 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(); // Command with result - validator as last generic parameter services.AddCommand(); } ``` **Benefits:** - **Single line registration** - Handler and validator registered together - **Type safety** - Compiler ensures validator matches command type - **Less boilerplate** - No need for separate `AddTransient>()` 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. | ⬜️ |