dotnet-cqrs/README.md

14 KiB
Raw Permalink Blame History

This project was originally initiated by Powered Software Inc. and was forked from the 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 dotnet add package Svrnty.CQRS
Svrnty.CQRS.MinimalApi NuGet dotnet add package Svrnty.CQRS.MinimalApi
Svrnty.CQRS.AspNetCore NuGet dotnet add package Svrnty.CQRS.AspNetCore
Svrnty.CQRS.FluentValidation NuGet dotnet add package Svrnty.CQRS.FluentValidation
Svrnty.CQRS.DynamicQuery NuGet dotnet add package Svrnty.CQRS.DynamicQuery
Svrnty.CQRS.DynamicQuery.AspNetCore NuGet dotnet add package Svrnty.CQRS.DynamicQuery.AspNetCore
Svrnty.CQRS.Grpc NuGet dotnet add package Svrnty.CQRS.Grpc
Svrnty.CQRS.Grpc.Generators NuGet dotnet add package Svrnty.CQRS.Grpc.Generators

Abstractions Packages.

Package Name NuGet NuGet Install
Svrnty.CQRS.Abstractions NuGet dotnet add package Svrnty.CQRS.Abstractions
Svrnty.CQRS.AspNetCore.Abstractions NuGet dotnet add package Svrnty.CQRS.AspNetCore.Abstractions
Svrnty.CQRS.DynamicQuery.Abstractions NuGet dotnet add package Svrnty.CQRS.DynamicQuery.Abstractions
Svrnty.CQRS.Grpc.Abstractions NuGet dotnet add package Svrnty.CQRS.Grpc.Abstractions
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:

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:

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:

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:

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:

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:

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.

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:

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>();
}

The package exposes extension method overloads that accept the validator as a generic parameter:

dotnet add package Svrnty.CQRS.FluentValidation
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.