Compare commits

...

2 Commits

Author SHA1 Message Date
f6e67986fa docs(cqrs): add architecture diagram, package index, and getting started guide
Co-Authored-By: Svrnty Inc. <jp@svrnty.io>
2026-03-08 14:02:19 -04:00
2c5059d947 docs(libraries): add library manifest and discovery index
Co-Authored-By: Svrnty Inc. <jp@svrnty.io>
2026-03-08 13:59:00 -04:00
6 changed files with 1066 additions and 5 deletions

14
.library-manifest.yaml Normal file
View File

@ -0,0 +1,14 @@
name: dotnet-cqrs
description: Modern CQRS framework for .NET with gRPC source generation and HTTP Minimal API support
owner: mathias@svrnty.io
layer: L3
stack: C# 14/.NET 10
status: stable
dependencies: []
dependents:
- flutter_cqrs_datasource
- a-gent-app
entry_points:
readme: README.md
registry: null
schemas: Svrnty.CQRS.sln

View File

@ -8,9 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- `.library-manifest.yaml` for cross-repo discovery and dependency tracking
- Initial project setup
- `docs/ARCHITECTURE.md` -- package dependency graph, CQRS data flows, saga flow, separation of concerns
- `docs/PACKAGE_INDEX.md` -- per-package reference for all 18 NuGet packages with key types and dependencies
- `docs/GETTING_STARTED.md` -- step-by-step guide covering handler registration, gRPC setup, MinimalApi, DynamicQuery, domain events, sagas, and notifications
### Changed
- Updated README.md to reflect correct package count (18), added links to new docs, added Related Libraries section linking to flutter_cqrs_datasource
### Fixed

View File

@ -30,15 +30,20 @@ dotnet test
## Architecture
10 NuGet packages organized by concern:
18 NuGet packages organized by concern:
- **Abstractions**: Core interfaces (ICommandHandler, IQueryHandler)
- **Core**: Discovery, registration, handler execution
- **Abstractions**: Core interfaces (ICommandHandler, IQueryHandler, IDomainEvent, ISaga, INotificationPublisher)
- **Core**: Discovery, registration, handler execution, CqrsBuilder fluent API
- **MinimalApi**: HTTP endpoint mapping with RFC 7807 validation
- **Grpc**: gRPC service support with Google Rich Error Model
- **Grpc.Generators**: Source generator for .proto files and service implementations
- **DynamicQuery**: PoweredSoft integration for filtering, sorting, paging
- **Grpc.Generators**: Roslyn source generator for .proto files and service implementations
- **DynamicQuery**: PoweredSoft integration for filtering, sorting, paging (with EF Core support)
- **FluentValidation**: Validator registration helpers
- **Events**: Domain event publishing (with RabbitMQ transport)
- **Sagas**: Saga orchestration pattern with compensation and distributed execution (with RabbitMQ transport)
- **Notifications**: Real-time notification streaming (with gRPC transport)
See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for a full dependency diagram and data flow.
## Configuration
@ -57,6 +62,16 @@ builder.Services.AddSvrntyCqrs(cqrs =>
app.UseSvrntyCqrs();
```
## Documentation
- [Architecture](./docs/ARCHITECTURE.md) -- Package dependency graph, CQRS data flows, separation of concerns
- [Package Index](./docs/PACKAGE_INDEX.md) -- Per-package reference with key types and dependencies
- [Getting Started](./docs/GETTING_STARTED.md) -- Step-by-step guide covering commands, queries, gRPC, DynamicQuery, events, sagas, and notifications
## Related Libraries
- **[flutter_cqrs_datasource](https://git.openharbor.io/svrnty/flutter_cqrs_datasource)** -- Flutter/Dart counterpart for consuming Svrnty.CQRS services from mobile and desktop apps
## Contributing
See [CLAUDE.md](./CLAUDE.md) for development guidelines.

178
docs/ARCHITECTURE.md Normal file
View File

@ -0,0 +1,178 @@
# Architecture
> Svrnty.CQRS is a modular CQRS/event-sourcing framework for .NET 10, organized as 18 NuGet packages with clear separation of concerns.
## Package Dependency Graph
```
Svrnty.CQRS.Abstractions
(ICommandHandler, IQueryHandler)
|
+-----------------+-----------------+
| |
Svrnty.CQRS Svrnty.CQRS.FluentValidation
(Discovery, Registration, (AbstractValidator<T> binding)
CqrsBuilder, DI) depends on: Abstractions, Core
|
+------------+------------+---------------------------+
| | | |
MinimalApi Grpc DynamicQuery Sagas
(HTTP REST) (gRPC) (Filtering, (Orchestrator,
| Sorting, Paging) Compensation)
| | |
Grpc.Abstractions DQ.Abstractions Sagas.Abstractions
(GrpcIgnore attr) (IQueryableProvider) (ISaga, ISagaBuilder,
| | | ISagaOrchestrator)
Grpc.Generators DQ.MinimalApi | |
(Source gen, (HTTP endpoints | Sagas.RabbitMQ
.proto gen) for DQ) | (RabbitMQ transport)
|
DQ.EntityFramework
(EF Core provider)
Events.Abstractions Notifications.Abstractions
(IDomainEvent, (INotificationPublisher,
IDomainEventPublisher) StreamingNotificationAttribute)
| |
Events.RabbitMQ Notifications.Grpc
(RabbitMQ transport) (gRPC streaming)
```
## Dependency Matrix
| Package | Depends On (internal) |
|---|---|
| `Svrnty.CQRS.Abstractions` | _(none)_ |
| `Svrnty.CQRS` | Abstractions |
| `Svrnty.CQRS.MinimalApi` | Abstractions, Core |
| `Svrnty.CQRS.Grpc` | Core |
| `Svrnty.CQRS.Grpc.Abstractions` | _(none)_ |
| `Svrnty.CQRS.Grpc.Generators` | _(none, Roslyn source gen)_ |
| `Svrnty.CQRS.FluentValidation` | Abstractions, Core |
| `Svrnty.CQRS.DynamicQuery.Abstractions` | _(none)_ |
| `Svrnty.CQRS.DynamicQuery` | DynamicQuery.Abstractions, Core |
| `Svrnty.CQRS.DynamicQuery.MinimalApi` | Abstractions, DynamicQuery.Abstractions, DynamicQuery |
| `Svrnty.CQRS.DynamicQuery.EntityFramework` | DynamicQuery |
| `Svrnty.CQRS.Events.Abstractions` | _(none)_ |
| `Svrnty.CQRS.Events.RabbitMQ` | Events.Abstractions |
| `Svrnty.CQRS.Sagas.Abstractions` | _(none)_ |
| `Svrnty.CQRS.Sagas` | Core, Sagas.Abstractions |
| `Svrnty.CQRS.Sagas.RabbitMQ` | Sagas |
| `Svrnty.CQRS.Notifications.Abstractions` | _(none)_ |
| `Svrnty.CQRS.Notifications.Grpc` | Notifications.Abstractions |
## CQRS Data Flow
### Command Flow
```
Client Request
|
v
[MinimalApi POST /api/command/{name}] or [gRPC CommandService/{name}]
|
v
FluentValidation (if validator registered)
|
|-- Validation fails --> RFC 7807 ProblemDetails (HTTP) / Google Rich Error (gRPC)
|
v
ICommandHandler<TCommand, TResult>.HandleAsync(command, ct)
|
v
Command Result (or void)
|
+--> (optional) IDomainEventPublisher.PublishAsync(event)
+--> (optional) INotificationPublisher.PublishAsync(notification)
```
### Query Flow
```
Client Request
|
v
[MinimalApi POST /api/query/{name}] or [gRPC QueryService/{name}]
|
v
IQueryHandler<TQuery, TResult>.HandleAsync(query, ct)
|
v
Query Result
```
### Dynamic Query Flow
```
Client Request (with filters, sorts, pagination)
|
v
[MinimalApi POST /api/dynamic-query/{entity}]
|
v
IQueryableProvider<TSource>.GetQueryableAsync(query, ct)
|
v
PoweredSoft.DynamicQuery engine (applies filters, sorts, groups, aggregates)
|
v
IAlterQueryableService (optional interception)
|
v
Paged/Grouped result set
```
### Saga Flow
```
ISagaOrchestrator.StartAsync<TSaga, TData>(data)
|
v
ISaga<TData>.Configure(builder) -- defines steps
|
v
Step 1: Execute --> Step 2: Execute --> Step 3: Execute --> Completed
| | |
| | +-- fails -->
| | |
| +-- compensate <-----------------+
| |
+-- compensate <-----------------+
|
v
Compensated (rolled back)
```
## Separation of Concerns
The framework follows a layered architecture:
1. **Abstractions layer** (4 packages) -- Pure interfaces and marker types with zero dependencies. Can be referenced by any project without pulling in implementation details.
- `Svrnty.CQRS.Abstractions`
- `Svrnty.CQRS.DynamicQuery.Abstractions`
- `Svrnty.CQRS.Events.Abstractions`
- `Svrnty.CQRS.Sagas.Abstractions`
- `Svrnty.CQRS.Grpc.Abstractions`
- `Svrnty.CQRS.Notifications.Abstractions`
2. **Core layer** (1 package) -- Handler discovery, DI registration, and the `CqrsBuilder` fluent API.
- `Svrnty.CQRS`
3. **Transport layer** (4 packages) -- Maps commands/queries to HTTP or gRPC endpoints.
- `Svrnty.CQRS.MinimalApi`
- `Svrnty.CQRS.Grpc`
- `Svrnty.CQRS.Grpc.Generators`
- `Svrnty.CQRS.DynamicQuery.MinimalApi`
4. **Feature layer** (4 packages) -- Optional capabilities that can be composed in.
- `Svrnty.CQRS.FluentValidation`
- `Svrnty.CQRS.DynamicQuery`
- `Svrnty.CQRS.DynamicQuery.EntityFramework`
- `Svrnty.CQRS.Sagas`
5. **Infrastructure layer** (3 packages) -- Concrete transport bindings for messaging and streaming.
- `Svrnty.CQRS.Events.RabbitMQ`
- `Svrnty.CQRS.Sagas.RabbitMQ`
- `Svrnty.CQRS.Notifications.Grpc`
This layering ensures that application code depends only on abstractions, while transport and infrastructure concerns remain pluggable.

514
docs/GETTING_STARTED.md Normal file
View File

@ -0,0 +1,514 @@
# Getting Started
> Step-by-step guide to building a CQRS application with Svrnty.CQRS on .NET 10.
## Prerequisites
- .NET 10 SDK
- A text editor or IDE with C# support
## 1. Create a New Project
```bash
dotnet new web -n MyCqrsApp
cd MyCqrsApp
```
Add the required packages:
```bash
dotnet add package Svrnty.CQRS
dotnet add package Svrnty.CQRS.Abstractions
dotnet add package Svrnty.CQRS.MinimalApi
dotnet add package Svrnty.CQRS.FluentValidation
```
## 2. Define Commands and Queries
### Command with Result
A command represents an action that changes state. Implement `ICommandHandler<TCommand, TResult>` for commands that return a value.
```csharp
using Svrnty.CQRS.Abstractions;
// The command (a plain record/class)
public record CreateUserCommand
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public int Age { get; set; }
}
// The handler
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
{
public Task<int> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken = default)
{
// Your business logic here -- persist to database, etc.
return Task.FromResult(123); // Return the new user ID
}
}
```
### Command without Result
For commands that do not return a value, implement `ICommandHandler<TCommand>`:
```csharp
public record DeleteUserCommand
{
public int UserId { get; set; }
}
public class DeleteUserCommandHandler : ICommandHandler<DeleteUserCommand>
{
public Task HandleAsync(DeleteUserCommand command, CancellationToken cancellationToken = default)
{
// Delete the user
return Task.CompletedTask;
}
}
```
### Query
A query retrieves data without side effects. Implement `IQueryHandler<TQuery, TResult>`:
```csharp
public record GetUserQuery
{
public int UserId { get; set; }
}
public record UserDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
public class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserDto>
{
public Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken = default)
{
return Task.FromResult(new UserDto
{
Id = query.UserId,
Name = "John Doe",
Email = "john@example.com"
});
}
}
```
## 3. Register Handlers
In `Program.cs`, register your handlers with the DI container:
```csharp
using Svrnty.CQRS;
using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.MinimalApi;
var builder = WebApplication.CreateBuilder(args);
// Register command and query handlers
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
builder.Services.AddCommand<DeleteUserCommand, DeleteUserCommandHandler>();
builder.Services.AddQuery<GetUserQuery, UserDto, GetUserQueryHandler>();
// Configure CQRS with MinimalApi transport
builder.Services.AddSvrntyCqrs(cqrs =>
{
cqrs.AddMinimalApi();
});
var app = builder.Build();
// Map all CQRS endpoints
app.UseSvrntyCqrs();
app.Run();
```
This will expose:
- `POST /api/command/CreateUser` -- executes CreateUserCommand
- `POST /api/command/DeleteUser` -- executes DeleteUserCommand
- `POST /api/query/GetUser` -- executes GetUserQuery
## 4. Add FluentValidation
Add validators to enforce business rules before handler execution:
```csharp
using FluentValidation;
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Email must be valid");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required");
RuleFor(x => x.Age)
.GreaterThan(0).WithMessage("Age must be greater than 0");
}
}
```
Register the command with its validator using the 4-type-parameter overload:
```csharp
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler, CreateUserCommandValidator>();
```
Validation errors are returned as RFC 7807 Problem Details (HTTP) or Google Rich Error Model (gRPC).
## 5. gRPC Setup
Add the gRPC packages:
```bash
dotnet add package Svrnty.CQRS.Grpc
dotnet add package Svrnty.CQRS.Grpc.Generators
dotnet add package Svrnty.CQRS.Grpc.Abstractions
```
Configure Kestrel for dual-protocol support and enable gRPC:
```csharp
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Svrnty.CQRS.Grpc;
var builder = WebApplication.CreateBuilder(args);
// Configure dual ports
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenLocalhost(6000, o => o.Protocols = HttpProtocols.Http2); // gRPC
options.ListenLocalhost(6001, o => o.Protocols = HttpProtocols.Http1); // HTTP API
});
// Register handlers (same as before)
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
builder.Services.AddQuery<GetUserQuery, UserDto, GetUserQueryHandler>();
// Enable both gRPC and MinimalApi
builder.Services.AddSvrntyCqrs(cqrs =>
{
cqrs.AddGrpc(grpc =>
{
grpc.EnableReflection(); // Enable gRPC reflection for tools like grpcurl
});
cqrs.AddMinimalApi();
});
var app = builder.Build();
app.UseSvrntyCqrs();
app.Run();
```
The `Svrnty.CQRS.Grpc.Generators` package automatically generates `.proto` files and gRPC service implementations from your registered command/query types at build time.
### Excluding Commands from gRPC
Use the `[GrpcIgnore]` attribute to prevent a command or query from being exposed via gRPC:
```csharp
using Svrnty.CQRS.Grpc.Abstractions.Attributes;
[GrpcIgnore]
public record InternalCommand
{
public string Data { get; set; } = string.Empty;
}
```
## 6. DynamicQuery Usage
Dynamic queries provide automatic filtering, sorting, grouping, and pagination for entity collections.
Add the packages:
```bash
dotnet add package Svrnty.CQRS.DynamicQuery
dotnet add package Svrnty.CQRS.DynamicQuery.Abstractions
dotnet add package Svrnty.CQRS.DynamicQuery.MinimalApi
```
### Define a Queryable Provider
Implement `IQueryableProvider<T>` to supply the data source:
```csharp
using Svrnty.CQRS.DynamicQuery.Abstractions;
public class UserQueryableProvider : IQueryableProvider<UserDto>
{
private readonly MyDbContext _db;
public UserQueryableProvider(MyDbContext db)
{
_db = db;
}
public Task<IQueryable<UserDto>> GetQueryableAsync(object query, CancellationToken cancellationToken = default)
{
return Task.FromResult(_db.Users.AsQueryable());
}
}
```
### Register the Provider
```csharp
using Svrnty.CQRS.DynamicQuery;
// Register PoweredSoft dependencies
builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, MyAsyncQueryableService>();
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
// Register the dynamic query provider
builder.Services.AddDynamicQueryWithProvider<UserDto, UserQueryableProvider>();
```
This exposes a POST endpoint that accepts filter, sort, group, and pagination parameters, returning paged results automatically.
### Entity Framework Integration
For EF Core projects, add the EF integration package:
```bash
dotnet add package Svrnty.CQRS.DynamicQuery.EntityFramework
```
This provides a ready-made `IAsyncQueryableService` backed by EF Core.
## 7. Domain Events
Domain events allow you to publish side effects after a command completes.
Add the packages:
```bash
dotnet add package Svrnty.CQRS.Events.Abstractions
dotnet add package Svrnty.CQRS.Events.RabbitMQ # or implement your own IDomainEventPublisher
```
### Define an Event
```csharp
using Svrnty.CQRS.Events.Abstractions;
public record UserCreatedEvent : IDomainEvent
{
public Guid EventId { get; } = Guid.NewGuid();
public DateTime OccurredAt { get; } = DateTime.UtcNow;
public int UserId { get; init; }
public string Email { get; init; } = string.Empty;
}
```
### Publish from a Command Handler
```csharp
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
{
private readonly IDomainEventPublisher _events;
public CreateUserCommandHandler(IDomainEventPublisher events)
{
_events = events;
}
public async Task<int> HandleAsync(CreateUserCommand command, CancellationToken ct = default)
{
var userId = 123; // persist user
await _events.PublishAsync(new UserCreatedEvent
{
UserId = userId,
Email = command.Email
}, ct);
return userId;
}
}
```
## 8. Saga Pattern
Sagas orchestrate multi-step workflows with automatic compensation (rollback) on failure.
Add the packages:
```bash
dotnet add package Svrnty.CQRS.Sagas
dotnet add package Svrnty.CQRS.Sagas.Abstractions
dotnet add package Svrnty.CQRS.Sagas.RabbitMQ # for distributed sagas
```
### Define Saga Data
```csharp
using Svrnty.CQRS.Sagas.Abstractions;
public class CreateOrderSagaData : ISagaData
{
public Guid CorrelationId { get; set; }
public int OrderId { get; set; }
public int PaymentId { get; set; }
public decimal Amount { get; set; }
}
```
### Define a Saga
```csharp
public class CreateOrderSaga : ISaga<CreateOrderSagaData>
{
public void Configure(ISagaBuilder<CreateOrderSagaData> builder)
{
builder
.Step("CreateOrder")
.Execute(async (data, ctx, ct) =>
{
// Create the order
data.OrderId = 42;
})
.Compensate(async (data, ctx, ct) =>
{
// Cancel the order on rollback
})
.Then()
.Step("ProcessPayment")
.Execute(async (data, ctx, ct) =>
{
// Charge payment
data.PaymentId = 99;
})
.Compensate(async (data, ctx, ct) =>
{
// Refund payment on rollback
})
.Then();
}
}
```
### Execute a Saga
```csharp
using Svrnty.CQRS.Sagas.Abstractions;
public class OrderCommandHandler : ICommandHandler<PlaceOrderCommand, int>
{
private readonly ISagaOrchestrator _orchestrator;
public OrderCommandHandler(ISagaOrchestrator orchestrator)
{
_orchestrator = orchestrator;
}
public async Task<int> HandleAsync(PlaceOrderCommand command, CancellationToken ct = default)
{
var state = await _orchestrator.StartAsync<CreateOrderSaga, CreateOrderSagaData>(
new CreateOrderSagaData { Amount = command.Amount }, ct);
// state.Status will be Completed or Compensated
return state.Status == SagaStatus.Completed ? 1 : 0;
}
}
```
Saga statuses: `NotStarted` -> `InProgress` -> `Completed` (success) or `Failed` -> `Compensating` -> `Compensated` (rolled back).
### Remote Steps (Distributed Sagas)
For steps that execute on remote services via RabbitMQ:
```csharp
builder
.SendCommand<ChargePaymentCommand, PaymentResult>("ChargePayment")
.WithCommand((data, ctx) => new ChargePaymentCommand { Amount = data.Amount })
.OnResponse(async (data, ctx, result, ct) =>
{
data.PaymentId = result.PaymentId;
})
.Compensate<RefundPaymentCommand>((data, ctx) =>
new RefundPaymentCommand { PaymentId = data.PaymentId })
.WithTimeout(TimeSpan.FromSeconds(30))
.WithRetry(maxRetries: 3, delay: TimeSpan.FromSeconds(2))
.Then();
```
## 9. Real-Time Notifications
For pushing real-time updates to clients via gRPC streaming:
```bash
dotnet add package Svrnty.CQRS.Notifications.Abstractions
dotnet add package Svrnty.CQRS.Notifications.Grpc
```
### Define a Notification
```csharp
using Svrnty.CQRS.Notifications.Abstractions;
[StreamingNotification(SubscriptionKey = "user-updates")]
public record UserUpdatedNotification
{
public int UserId { get; init; }
public string NewEmail { get; init; } = string.Empty;
}
```
### Publish a Notification
```csharp
public class UpdateUserCommandHandler : ICommandHandler<UpdateUserCommand>
{
private readonly INotificationPublisher _notifications;
public UpdateUserCommandHandler(INotificationPublisher notifications)
{
_notifications = notifications;
}
public async Task HandleAsync(UpdateUserCommand command, CancellationToken ct = default)
{
// Update user...
await _notifications.PublishAsync(new UserUpdatedNotification
{
UserId = command.UserId,
NewEmail = command.NewEmail
}, ct);
}
}
```
## Running the Sample App
The repository includes a complete sample application:
```bash
cd Svrnty.Sample
dotnet run
```
This starts:
- gRPC server on `http://localhost:6000` (HTTP/2)
- HTTP API on `http://localhost:6001` (HTTP/1.1)
- Swagger UI at `http://localhost:6001/swagger`
The sample demonstrates commands with validation, queries, gRPC reflection, MinimalApi endpoints, and dynamic queries.

335
docs/PACKAGE_INDEX.md Normal file
View File

@ -0,0 +1,335 @@
# Package Index
> Complete reference for all 18 NuGet packages in the Svrnty.CQRS framework.
## Overview
| # | Package | Path | NuGet Package |
|---|---------|------|:---:|
| 1 | [Svrnty.CQRS.Abstractions](#1-svrntycqrsabstractions) | `Svrnty.CQRS.Abstractions/` | Yes |
| 2 | [Svrnty.CQRS](#2-svrntycqrs) | `Svrnty.CQRS/` | Yes |
| 3 | [Svrnty.CQRS.MinimalApi](#3-svrntycqrsminimalapi) | `Svrnty.CQRS.MinimalApi/` | Yes |
| 4 | [Svrnty.CQRS.Grpc](#4-svrntycqrsgrpc) | `Svrnty.CQRS.Grpc/` | Yes |
| 5 | [Svrnty.CQRS.Grpc.Abstractions](#5-svrntycqrsgrpcabstractions) | `Svrnty.CQRS.Grpc.Abstractions/` | Yes |
| 6 | [Svrnty.CQRS.Grpc.Generators](#6-svrntycqrsgrpcgenerators) | `Svrnty.CQRS.Grpc.Generators/` | Yes |
| 7 | [Svrnty.CQRS.FluentValidation](#7-svrntycqrsfluentvalidation) | `Svrnty.CQRS.FluentValidation/` | Yes |
| 8 | [Svrnty.CQRS.DynamicQuery.Abstractions](#8-svrntycqrsdynamicqueryabstractions) | `Svrnty.CQRS.DynamicQuery.Abstractions/` | Yes |
| 9 | [Svrnty.CQRS.DynamicQuery](#9-svrntycqrsdynamicquery) | `Svrnty.CQRS.DynamicQuery/` | Yes |
| 10 | [Svrnty.CQRS.DynamicQuery.MinimalApi](#10-svrntycqrsdynamicqueryminimalapi) | `Svrnty.CQRS.DynamicQuery.MinimalApi/` | Yes |
| 11 | [Svrnty.CQRS.DynamicQuery.EntityFramework](#11-svrntycqrsdynamicqueryentityframework) | `Svrnty.CQRS.DynamicQuery.EntityFramework/` | Yes |
| 12 | [Svrnty.CQRS.Events.Abstractions](#12-svrntycqrseventsabstractions) | `Svrnty.CQRS.Events.Abstractions/` | Yes |
| 13 | [Svrnty.CQRS.Events.RabbitMQ](#13-svrntycqrseventsrabbitmq) | `Svrnty.CQRS.Events.RabbitMQ/` | Yes |
| 14 | [Svrnty.CQRS.Sagas.Abstractions](#14-svrntycqrssagasabstractions) | `Svrnty.CQRS.Sagas.Abstractions/` | Yes |
| 15 | [Svrnty.CQRS.Sagas](#15-svrntycqrssagas) | `Svrnty.CQRS.Sagas/` | Yes |
| 16 | [Svrnty.CQRS.Sagas.RabbitMQ](#16-svrntycqrssagasrabbitmq) | `Svrnty.CQRS.Sagas.RabbitMQ/` | Yes |
| 17 | [Svrnty.CQRS.Notifications.Abstractions](#17-svrntycqrsnotificationsabstractions) | `Svrnty.CQRS.Notifications.Abstractions/` | Yes |
| 18 | [Svrnty.CQRS.Notifications.Grpc](#18-svrntycqrsnotificationsgrpc) | `Svrnty.CQRS.Notifications.Grpc/` | Yes |
---
## Package Details
### 1. Svrnty.CQRS.Abstractions
**Purpose**: Core interfaces that define the CQRS contract. This is the only package your domain/application layer needs to reference.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `ICommandHandler<TCommand>` -- Handler for commands with no return value
- `ICommandHandler<TCommand, TResult>` -- Handler for commands returning a result
- `IQueryHandler<TQuery, TResult>` -- Handler for queries
- `ICommandMeta` / `IQueryMeta` -- Discovery metadata
- `ICommandDiscovery` / `IQueryDiscovery` -- Service discovery interfaces
- `ICommandAuthorizationService<TCommand>` -- Per-command authorization
- `IQueryAuthorizationService<TQuery>` -- Per-query authorization
- `CommandNameAttribute` / `QueryNameAttribute` -- Custom naming
- `IgnoreCommandAttribute` / `IgnoreQueryAttribute` -- Exclude from auto-discovery
**Internal Dependencies**: None
---
### 2. Svrnty.CQRS
**Purpose**: Core registration and discovery engine. Provides the `AddSvrntyCqrs()` fluent API and auto-discovers registered handlers.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `CqrsBuilder` -- Fluent builder for configuring transports and features
- `CqrsConfiguration` -- Configuration state
- `ServiceCollectionExtensions.AddSvrntyCqrs()` -- Entry point
- `ServiceCollectionExtensions.AddCommand<T, TResult, THandler>()` -- Register a command handler
- `ServiceCollectionExtensions.AddQuery<T, TResult, THandler>()` -- Register a query handler
- `CommandDiscovery` / `QueryDiscovery` -- Default discovery implementations
**Internal Dependencies**: `Svrnty.CQRS.Abstractions`
---
### 3. Svrnty.CQRS.MinimalApi
**Purpose**: Maps registered commands and queries to ASP.NET Core Minimal API HTTP endpoints. Includes RFC 7807 Problem Details for validation errors.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- `CqrsBuilderExtensions.AddMinimalApi()` -- Enable HTTP endpoints
- `MinimalApiCqrsOptions` -- Configuration (route prefixes, etc.)
- `ValidationFilter` -- Endpoint filter for FluentValidation
- `WebApplicationExtensions.UseSvrntyCqrs()` -- Map endpoints at startup
- `EndpointRouteBuilderExtensions` -- Route mapping helpers
**Internal Dependencies**: `Svrnty.CQRS.Abstractions`, `Svrnty.CQRS`
**External Dependencies**: `FluentValidation 11.x`, `Microsoft.AspNetCore.App`
---
### 4. Svrnty.CQRS.Grpc
**Purpose**: Maps registered commands and queries to gRPC services. Uses Google Rich Error Model for structured validation errors.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- `CqrsBuilderExtensions.AddGrpc()` -- Enable gRPC endpoints
- `GrpcCqrsOptions` -- Configuration (reflection, etc.)
**Internal Dependencies**: `Svrnty.CQRS`
**External Dependencies**: `Grpc.AspNetCore 2.71.0`
---
### 5. Svrnty.CQRS.Grpc.Abstractions
**Purpose**: Attributes for controlling gRPC code generation behavior.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `GrpcIgnoreAttribute` -- Marks a command/query to be excluded from gRPC service generation
**Internal Dependencies**: None
---
### 6. Svrnty.CQRS.Grpc.Generators
**Purpose**: Roslyn source generator that auto-generates `.proto` files and gRPC service implementations from registered command/query types.
**Target**: `netstandard2.0` (Roslyn component) | **AOT**: N/A
**Key Types**:
- Source generator (analyzer DLL)
- MSBuild `WriteProtoFileTask` -- Writes generated `.proto` files to disk
- Build targets and props for NuGet consumers
**Internal Dependencies**: None (ships as analyzer)
**External Dependencies**: `Microsoft.CodeAnalysis.CSharp 5.0.0`, `Microsoft.Build.Utilities.Core 17.0.0`
---
### 7. Svrnty.CQRS.FluentValidation
**Purpose**: Integrates FluentValidation with command/query registration. Validators are automatically invoked before handler execution.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `ServiceCollectionExtensions.AddCommand<TCmd, TResult, THandler, TValidator>()` -- Register command with validator
- Automatic `AbstractValidator<T>` binding
**Internal Dependencies**: `Svrnty.CQRS`, `Svrnty.CQRS.Abstractions`
**External Dependencies**: `FluentValidation 11.11.0`
---
### 8. Svrnty.CQRS.DynamicQuery.Abstractions
**Purpose**: Interfaces for the dynamic query subsystem. Defines how data sources are provided and queries are intercepted.
**Target**: `netstandard2.1`, `net10.0` (multi-target) | **AOT**: Conditional
**Key Types**:
- `IQueryableProvider<TSource>` -- Provides an `IQueryable<T>` data source
- `IQueryableProviderOverride<TSource>` -- Override default provider
- `IAlterQueryableService<TSource>` -- Intercept/modify queryables
- `IDynamicQuery` / `IDynamicQueryParams` -- Query parameter contracts
- `IDynamicQueryInterceptorProvider` -- Interceptor registration
**Internal Dependencies**: None
**External Dependencies**: `PoweredSoft.DynamicQuery.Core 3.0.1`
---
### 9. Svrnty.CQRS.DynamicQuery
**Purpose**: Implementation of dynamic query execution with filtering, sorting, grouping, pagination, and aggregation.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `ServiceCollectionExtensions.AddDynamicQueryWithProvider<TSource, TProvider>()` -- Register a queryable provider
- Dynamic query handler pipeline
**Internal Dependencies**: `Svrnty.CQRS.DynamicQuery.Abstractions`, `Svrnty.CQRS`
**External Dependencies**: `PoweredSoft.DynamicQuery 3.0.1`, `Pluralize.NET 1.0.2`
---
### 10. Svrnty.CQRS.DynamicQuery.MinimalApi
**Purpose**: HTTP Minimal API endpoints for dynamic queries. Exposes each registered entity as a POST endpoint with filter/sort/page parameters.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- Endpoint mapping for dynamic query routes (`/api/dynamic-query/{entity}`)
**Internal Dependencies**: `Svrnty.CQRS.Abstractions`, `Svrnty.CQRS.DynamicQuery.Abstractions`, `Svrnty.CQRS.DynamicQuery`
**External Dependencies**: `Microsoft.AspNetCore.App`
---
### 11. Svrnty.CQRS.DynamicQuery.EntityFramework
**Purpose**: Entity Framework Core integration for dynamic queries. Provides an EF-backed `IAsyncQueryableService`.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- EF Core queryable service adapter
**Internal Dependencies**: `Svrnty.CQRS.DynamicQuery`
**External Dependencies**: `PoweredSoft.Data.EntityFrameworkCore 3.0.0`
---
### 12. Svrnty.CQRS.Events.Abstractions
**Purpose**: Interfaces for domain event publishing.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `IDomainEvent` -- Marker interface (EventId, OccurredAt)
- `IDomainEventPublisher` -- Publish events to external systems
**Internal Dependencies**: None
---
### 13. Svrnty.CQRS.Events.RabbitMQ
**Purpose**: RabbitMQ-backed implementation of domain event publishing.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- RabbitMQ event publisher implementation
**Internal Dependencies**: `Svrnty.CQRS.Events.Abstractions`
**External Dependencies**: `RabbitMQ.Client 7.0.0`, `Microsoft.Extensions.DependencyInjection.Abstractions`, `Microsoft.Extensions.Logging.Abstractions`, `Microsoft.Extensions.Options`
---
### 14. Svrnty.CQRS.Sagas.Abstractions
**Purpose**: Interfaces and types for the saga orchestration pattern with compensation (rollback) support.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `ISaga<TData>` -- Define a saga with steps
- `ISagaBuilder<TData>` -- Fluent builder for local and remote steps
- `ISagaStepBuilder<TData>` -- Configure Execute/Compensate actions
- `ISagaRemoteStepBuilder<TData, TCommand>` -- Remote command steps with timeout/retry
- `ISagaOrchestrator` -- Start sagas, query state
- `ISagaData` -- Marker interface (CorrelationId)
- `SagaState` -- Persistent saga state (status, completed steps, errors)
- `SagaStatus` -- Enum: NotStarted, InProgress, Completed, Failed, Compensating, Compensated
- `ISagaStateStore` -- Persistence abstraction
- `ISagaMessageBus` -- Messaging abstraction
- `SagaMessage` / `SagaStepResponse` -- Message types
- `ISagaContext` -- Step execution context
**Internal Dependencies**: None
---
### 15. Svrnty.CQRS.Sagas
**Purpose**: Default saga orchestrator implementation with step execution, compensation, and state management.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- Saga orchestrator engine
- In-memory state store (default)
**Internal Dependencies**: `Svrnty.CQRS`, `Svrnty.CQRS.Sagas.Abstractions`
**External Dependencies**: `Microsoft.Extensions.Logging.Abstractions`, `Microsoft.Extensions.Options`
---
### 16. Svrnty.CQRS.Sagas.RabbitMQ
**Purpose**: RabbitMQ-backed message bus for distributed saga step execution across microservices.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- RabbitMQ saga message bus implementation
**Internal Dependencies**: `Svrnty.CQRS.Sagas`
**External Dependencies**: `RabbitMQ.Client 7.0.0`, `Microsoft.Extensions.Hosting.Abstractions`, `Microsoft.Extensions.Options`
---
### 17. Svrnty.CQRS.Notifications.Abstractions
**Purpose**: Interfaces for real-time notification streaming to clients.
**Target**: `net10.0` | **AOT**: Yes
**Key Types**:
- `INotificationPublisher` -- Publish notifications to subscribed clients
- `StreamingNotificationAttribute` -- Marks a type as a streamable notification with a subscription key
**Internal Dependencies**: None
---
### 18. Svrnty.CQRS.Notifications.Grpc
**Purpose**: gRPC server-streaming implementation for real-time notifications.
**Target**: `net10.0` | **AOT**: No
**Key Types**:
- gRPC notification streaming service
**Internal Dependencies**: `Svrnty.CQRS.Notifications.Abstractions`
**External Dependencies**: `Grpc.AspNetCore 2.71.0`, `Microsoft.Extensions.DependencyInjection.Abstractions`, `Microsoft.Extensions.Logging.Abstractions`
---
## Additional Projects (not NuGet packages)
| Project | Path | Purpose |
|---------|------|---------|
| `Svrnty.Sample` | `Svrnty.Sample/` | Sample web application demonstrating commands, queries, gRPC, MinimalApi, DynamicQuery, and validation |
| `Svrnty.CQRS.Tests` | `tests/Svrnty.CQRS.Tests/` | Unit and integration test suite |