update readme
This commit is contained in:
parent
467e700885
commit
e72cbe4319
201
README.md
201
README.md
@ -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 without result - validator included in generics
|
|
||||||
services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
|
|
||||||
|
|
||||||
// Command with result - validator as last generic parameter
|
// Command with result - validator as last generic parameter
|
||||||
services.AddCommand<CreatePersonCommand, CreatePersonCommandHandler, CreatePersonCommandValidator>();
|
builder.Services.AddCommand<EchoCommand, string, EchoCommandHandler, EchoCommandValidator>();
|
||||||
}
|
|
||||||
|
// Command without result - validator included in generics
|
||||||
|
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 |
|
||||||
|
|||||||
@ -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 =>
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user