update readme

This commit is contained in:
Mathias Beaulieu-Duncan 2025-11-07 13:34:51 -05:00
parent 467e700885
commit e72cbe4319
2 changed files with 92 additions and 119 deletions

201
README.md
View File

@ -30,28 +30,33 @@ Our implementation of query and command responsibility segregation (CQRS).
## Sample of startup code for gRPC (Recommended) ## Sample of startup code for gRPC (Recommended)
```csharp ```csharp
using Svrnty.CQRS;
using Svrnty.CQRS.FluentValidation;
using Svrnty.CQRS.Grpc;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Register CQRS core services // Register your commands with validators
builder.Services.AddSvrntyCQRS(); builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
builder.Services.AddDefaultQueryDiscovery();
// Add your commands and queries // Register your queries
AddQueries(builder.Services); builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
AddCommands(builder.Services);
// Add gRPC support // Configure CQRS with gRPC support
builder.Services.AddGrpc(); builder.Services.AddSvrntyCqrs(cqrs =>
{
// Enable gRPC endpoints with reflection
cqrs.AddGrpc(grpc =>
{
grpc.EnableReflection();
});
});
var app = builder.Build(); var app = builder.Build();
// Map auto-generated gRPC service implementations // Map all configured CQRS endpoints
app.MapGrpcService<CommandServiceImpl>(); app.UseSvrntyCqrs();
app.MapGrpcService<QueryServiceImpl>();
// Enable gRPC reflection for tools like grpcurl
app.MapGrpcReflectionService();
app.Run(); app.Run();
``` ```
@ -74,31 +79,9 @@ dotnet add package Grpc.StatusProto # For Rich Error Model validation
dotnet add package Svrnty.CQRS.Grpc.Generators 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: #### 3. Define your C# commands and queries:
```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 ```csharp
public record AddUserCommand public record AddUserCommand
@ -115,28 +98,38 @@ public record RemoveUserCommand
``` ```
**Notes:** **Notes:**
- The source generator automatically creates `CommandServiceImpl` and `QueryServiceImpl` implementations - The source generator automatically creates:
- Property names in C# commands must match proto field names (case-insensitive) - `.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 - FluentValidation is automatically integrated with **Google Rich Error Model** for structured validation errors
- Validation errors return `google.rpc.Status` with `BadRequest` containing `FieldViolations` - Validation errors return `google.rpc.Status` with `BadRequest` containing `FieldViolations`
- Use `record` types for commands/queries (immutable, value-based equality, more concise) - 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) ## Sample of startup code for Minimal API (HTTP)
For HTTP scenarios (web browsers, public APIs), you can use the Minimal API approach: For HTTP scenarios (web browsers, public APIs), you can use the Minimal API approach:
```csharp ```csharp
using Svrnty.CQRS;
using Svrnty.CQRS.FluentValidation;
using Svrnty.CQRS.MinimalApi;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Register CQRS core services // Register your commands with validators
builder.Services.AddSvrntyCQRS(); builder.Services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler, CreatePersonCommandValidator>();
builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
builder.Services.AddDefaultQueryDiscovery();
// Add your commands and queries // Register your queries
AddQueries(builder.Services); builder.Services.AddQuery<PersonQuery, IQueryable<Person>, PersonQueryHandler>();
AddCommands(builder.Services);
// Configure CQRS with Minimal API support
builder.Services.AddSvrntyCqrs(cqrs =>
{
// Enable Minimal API endpoints
cqrs.AddMinimalApi();
});
// Add Swagger (optional) // Add Swagger (optional)
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
@ -150,9 +143,8 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI(); app.UseSwaggerUI();
} }
// Map CQRS endpoints - automatically creates routes for all commands and queries // Map all configured CQRS endpoints (automatically creates POST /api/command/* and POST/GET /api/query/*)
app.MapSvrntyCommands(); // Creates POST /api/command/{commandName} endpoints app.UseSvrntyCqrs();
app.MapSvrntyQueries(); // Creates POST/GET /api/query/{queryName} endpoints
app.Run(); 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: You can enable both gRPC and traditional HTTP endpoints simultaneously, allowing clients to choose their preferred protocol:
```csharp ```csharp
using Svrnty.CQRS;
using Svrnty.CQRS.FluentValidation;
using Svrnty.CQRS.Grpc;
using Svrnty.CQRS.MinimalApi;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Register CQRS core services // Register your commands with validators
builder.Services.AddSvrntyCQRS(); builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
builder.Services.AddDefaultQueryDiscovery();
// Add your commands and queries // Register your queries
AddQueries(builder.Services); builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
AddCommands(builder.Services);
// Add gRPC support // Configure CQRS with both gRPC and Minimal API support
builder.Services.AddGrpc(); 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 // Add HTTP support with Swagger
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
@ -194,14 +199,8 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI(); app.UseSwaggerUI();
} }
// Map gRPC endpoints // Map all configured CQRS endpoints (both gRPC and HTTP)
app.MapGrpcService<CommandServiceImpl>(); app.UseSvrntyCqrs();
app.MapGrpcService<QueryServiceImpl>();
app.MapGrpcReflectionService();
// Map HTTP endpoints
app.MapSvrntyCommands();
app.MapSvrntyQueries();
app.Run(); app.Run();
``` ```
@ -213,47 +212,10 @@ app.Run();
- Same commands, queries, and validation logic for both protocols - Same commands, queries, and validation logic for both protocols
- Swagger UI available for HTTP endpoints, gRPC reflection for gRPC clients - 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 # Fluent Validation
FluentValidation is optional but recommended for command and query validation. The `Svrnty.CQRS.FluentValidation` package provides extension methods to simplify validator registration. 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) ## With Svrnty.CQRS.FluentValidation (Recommended)
The package exposes extension method overloads that accept the validator as a generic parameter: 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 ```csharp
using Microsoft.Extensions.DependencyInjection;
using Svrnty.CQRS.FluentValidation; // Extension methods for validator registration using Svrnty.CQRS.FluentValidation; // Extension methods for validator registration
private void AddCommands(IServiceCollection services) // Command with result - validator as last generic parameter
{ builder.Services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
// Command without result - validator included in generics
services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
// Command with result - validator as last generic parameter // Command without result - validator included in generics
services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler, CreatePersonCommandValidator>(); builder.Services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler, CreatePersonCommandValidator>();
}
``` ```
**Benefits:** **Benefits:**
@ -282,6 +240,21 @@ private void AddCommands(IServiceCollection services)
- **Less boilerplate** - No need for separate `AddTransient<IValidator<T>>()` calls - **Less boilerplate** - No need for separate `AddTransient<IValidator<T>>()` calls
- **Cleaner code** - Clear intent that validation is part of command pipeline - **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<EchoCommand, string, EchoCommandHandler>();
// Manually register validator
builder.Services.AddTransient<IValidator<EchoCommand>, EchoCommandValidator>();
```
# 2024-2025 Roadmap # 2024-2025 Roadmap
| Task | Description | Status | | Task | Description | Status |

View File

@ -24,14 +24,14 @@ builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, Simp
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>(); builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>(); builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>();
// Register commands and queries with validators
builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
// Configure CQRS with fluent API // Configure CQRS with fluent API
builder.Services.AddSvrntyCqrs(cqrs => builder.Services.AddSvrntyCqrs(cqrs =>
{ {
// Register commands and queries with validators
cqrs.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
cqrs.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
cqrs.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
// Enable gRPC endpoints with reflection // Enable gRPC endpoints with reflection
cqrs.AddGrpc(grpc => cqrs.AddGrpc(grpc =>
{ {