From e72cbe4319f24a52324273b9bdb0724ab0b25195 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Fri, 7 Nov 2025 13:34:51 -0500 Subject: [PATCH] update readme --- README.md | 201 +++++++++++++++++---------------------- Svrnty.Sample/Program.cs | 10 +- 2 files changed, 92 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index f1c64ee..dc8d04e 100644 --- a/README.md +++ b/README.md @@ -30,28 +30,33 @@ Our implementation of query and command responsibility segregation (CQRS). ## Sample of startup code for gRPC (Recommended) ```csharp +using Svrnty.CQRS; +using Svrnty.CQRS.FluentValidation; +using Svrnty.CQRS.Grpc; + var builder = WebApplication.CreateBuilder(args); -// Register CQRS core services -builder.Services.AddSvrntyCQRS(); -builder.Services.AddDefaultCommandDiscovery(); -builder.Services.AddDefaultQueryDiscovery(); +// Register your commands with validators +builder.Services.AddCommand(); +builder.Services.AddCommand(); -// Add your commands and queries -AddQueries(builder.Services); -AddCommands(builder.Services); +// Register your queries +builder.Services.AddQuery(); -// Add gRPC support -builder.Services.AddGrpc(); +// Configure CQRS with gRPC support +builder.Services.AddSvrntyCqrs(cqrs => +{ + // Enable gRPC endpoints with reflection + cqrs.AddGrpc(grpc => + { + grpc.EnableReflection(); + }); +}); var app = builder.Build(); -// Map auto-generated gRPC service implementations -app.MapGrpcService(); -app.MapGrpcService(); - -// Enable gRPC reflection for tools like grpcurl -app.MapGrpcReflectionService(); +// Map all configured CQRS endpoints +app.UseSvrntyCqrs(); app.Run(); ``` @@ -74,31 +79,9 @@ dotnet add package Grpc.StatusProto # For Rich Error Model validation 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. +The source generator is automatically configured as an analyzer when installed via NuGet and will generate both the `.proto` files and 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: +#### 3. Define your C# commands and queries: ```csharp public record AddUserCommand @@ -115,28 +98,38 @@ public record RemoveUserCommand ``` **Notes:** -- The source generator automatically creates `CommandServiceImpl` and `QueryServiceImpl` implementations -- Property names in C# commands must match proto field names (case-insensitive) +- The source generator automatically creates: + - `.proto` files in the `Protos/` directory from your C# commands and queries + - `CommandServiceImpl` and `QueryServiceImpl` implementations - 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 +- No need for protobuf-net attributes - just define your C# types ## Sample of startup code for Minimal API (HTTP) For HTTP scenarios (web browsers, public APIs), you can use the Minimal API approach: ```csharp +using Svrnty.CQRS; +using Svrnty.CQRS.FluentValidation; +using Svrnty.CQRS.MinimalApi; + var builder = WebApplication.CreateBuilder(args); -// Register CQRS core services -builder.Services.AddSvrntyCQRS(); -builder.Services.AddDefaultCommandDiscovery(); -builder.Services.AddDefaultQueryDiscovery(); +// Register your commands with validators +builder.Services.AddCommand(); +builder.Services.AddCommand(); -// Add your commands and queries -AddQueries(builder.Services); -AddCommands(builder.Services); +// Register your queries +builder.Services.AddQuery, PersonQueryHandler>(); + +// Configure CQRS with Minimal API support +builder.Services.AddSvrntyCqrs(cqrs => +{ + // Enable Minimal API endpoints + cqrs.AddMinimalApi(); +}); // Add Swagger (optional) builder.Services.AddEndpointsApiExplorer(); @@ -150,9 +143,8 @@ if (app.Environment.IsDevelopment()) 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 +// Map all configured CQRS endpoints (automatically creates POST /api/command/* and POST/GET /api/query/*) +app.UseSvrntyCqrs(); app.Run(); ``` @@ -168,19 +160,32 @@ app.Run(); You can enable both gRPC and traditional HTTP endpoints simultaneously, allowing clients to choose their preferred protocol: ```csharp +using Svrnty.CQRS; +using Svrnty.CQRS.FluentValidation; +using Svrnty.CQRS.Grpc; +using Svrnty.CQRS.MinimalApi; + var builder = WebApplication.CreateBuilder(args); -// Register CQRS core services -builder.Services.AddSvrntyCQRS(); -builder.Services.AddDefaultCommandDiscovery(); -builder.Services.AddDefaultQueryDiscovery(); +// Register your commands with validators +builder.Services.AddCommand(); +builder.Services.AddCommand(); -// Add your commands and queries -AddQueries(builder.Services); -AddCommands(builder.Services); +// Register your queries +builder.Services.AddQuery(); -// Add gRPC support -builder.Services.AddGrpc(); +// Configure CQRS with both gRPC and Minimal API support +builder.Services.AddSvrntyCqrs(cqrs => +{ + // Enable gRPC endpoints with reflection + cqrs.AddGrpc(grpc => + { + grpc.EnableReflection(); + }); + + // Enable Minimal API endpoints + cqrs.AddMinimalApi(); +}); // Add HTTP support with Swagger builder.Services.AddEndpointsApiExplorer(); @@ -194,14 +199,8 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } -// Map gRPC endpoints -app.MapGrpcService(); -app.MapGrpcService(); -app.MapGrpcReflectionService(); - -// Map HTTP endpoints -app.MapSvrntyCommands(); -app.MapSvrntyQueries(); +// Map all configured CQRS endpoints (both gRPC and HTTP) +app.UseSvrntyCqrs(); app.Run(); ``` @@ -213,47 +212,10 @@ app.Run(); - 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: @@ -263,17 +225,13 @@ 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 +builder.Services.AddCommand(); - // Command with result - validator as last generic parameter - services.AddCommand(); -} +// Command without result - validator included in generics +builder.Services.AddCommand(); ``` **Benefits:** @@ -282,6 +240,21 @@ private void AddCommands(IServiceCollection services) - **Less boilerplate** - No need for separate `AddTransient>()` calls - **Cleaner code** - Clear intent that validation is part of command pipeline +## Without Svrnty.CQRS.FluentValidation + +If you prefer not to use the FluentValidation package, you need to register commands and validators separately: + +```csharp +using FluentValidation; +using Svrnty.CQRS; + +// Register command handler +builder.Services.AddCommand(); + +// Manually register validator +builder.Services.AddTransient, EchoCommandValidator>(); +``` + # 2024-2025 Roadmap | Task | Description | Status | diff --git a/Svrnty.Sample/Program.cs b/Svrnty.Sample/Program.cs index c9b9246..dda0ff4 100644 --- a/Svrnty.Sample/Program.cs +++ b/Svrnty.Sample/Program.cs @@ -24,14 +24,14 @@ builder.Services.AddTransient(); builder.Services.AddDynamicQueryWithProvider(); +// Register commands and queries with validators +builder.Services.AddCommand(); +builder.Services.AddCommand(); +builder.Services.AddQuery(); + // Configure CQRS with fluent API builder.Services.AddSvrntyCqrs(cqrs => { - // Register commands and queries with validators - cqrs.AddCommand(); - cqrs.AddCommand(); - cqrs.AddQuery(); - // Enable gRPC endpoints with reflection cqrs.AddGrpc(grpc => {