# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is Svrnty.CQRS, a modern implementation of Command Query Responsibility Segregation (CQRS) for .NET 10. It was forked from PoweredSoft.CQRS and provides: - Automatic REST endpoint generation from command/query handlers - Dynamic query capabilities (filtering, sorting, grouping, aggregation) - ASP.NET Core MVC integration - FluentValidation support - AOT (Ahead-of-Time) compilation compatibility for core packages ## Solution Structure The solution contains 9 projects organized by responsibility: **Abstractions (interfaces and contracts only):** - `Svrnty.CQRS.Abstractions` - Core interfaces (ICommandHandler, IQueryHandler, discovery contracts) - `Svrnty.CQRS.AspNetCore.Abstractions` - ASP.NET Core attributes - `Svrnty.CQRS.DynamicQuery.Abstractions` - Dynamic query interfaces (multi-targets netstandard2.1 and net10.0) **Implementation:** - `Svrnty.CQRS` - Core discovery and registration logic - `Svrnty.CQRS.AspNetCore` - MVC controller generation (legacy/backward compatibility) - `Svrnty.CQRS.MinimalApi` - Minimal API endpoint mapping (recommended for new projects) - `Svrnty.CQRS.DynamicQuery` - PoweredSoft.DynamicQuery integration - `Svrnty.CQRS.DynamicQuery.AspNetCore` - Dynamic query controllers - `Svrnty.CQRS.FluentValidation` - Validation integration helpers **Key Design Principle:** Abstractions projects contain ONLY interfaces/attributes with minimal dependencies. Implementation projects depend on abstractions. This allows consumers to reference abstractions without pulling in heavy implementation dependencies. ## Build Commands ```bash # Restore dependencies dotnet restore # Build entire solution dotnet build # Build in Release mode dotnet build -c Release # Create NuGet packages (with version) dotnet pack -c Release -o ./artifacts -p:Version=1.0.0 # Build specific project dotnet build Svrnty.CQRS/Svrnty.CQRS.csproj ``` ## Testing This repository does not currently contain test projects. When adding tests: - Place them in a `tests/` directory or alongside source projects - Name them with `.Tests` suffix (e.g., `Svrnty.CQRS.Tests`) ## Architecture ### Core CQRS Pattern The framework uses handler interfaces that follow this pattern: ```csharp // Command with no result ICommandHandler Task HandleAsync(TCommand command, CancellationToken cancellationToken = default) // Command with result ICommandHandler Task HandleAsync(TCommand command, CancellationToken cancellationToken = default) // Query (always returns result) IQueryHandler Task HandleAsync(TQuery query, CancellationToken cancellationToken = default) ``` ### Metadata-Driven Discovery The framework uses a **metadata pattern** for runtime discovery: 1. When you register a handler using `services.AddCommand()`, it: - Registers the handler in DI as `ICommandHandler` - Creates metadata (`ICommandMeta`) describing the command type, handler type, and result type - Stores metadata as singleton in DI 2. Discovery services (`ICommandDiscovery`, `IQueryDiscovery`) implemented in `Svrnty.CQRS`: - Query all registered metadata from DI container - Provide lookup methods: `GetCommand(string name)`, `GetCommands()`, etc. 3. ASP.NET Core feature providers use discovery to: - Enumerate all registered commands/queries - Dynamically generate generic controller instances at startup - Apply naming conventions (convert to lowerCamelCase) **Key Files:** - `Svrnty.CQRS.Abstractions/Discovery/` - Metadata interfaces - `Svrnty.CQRS/Discovery/` - Discovery implementations - `Svrnty.CQRS.AspNetCore/Mvc/*FeatureProvider.cs` - Dynamic controller generation - `Svrnty.CQRS.AspNetCore/Mvc/*Convention.cs` - Naming conventions ### ASP.NET Core Integration Options There are two options for integrating commands and queries with ASP.NET Core: #### Option 1: Minimal API (Recommended) The **Svrnty.CQRS.MinimalApi** package provides modern Minimal API endpoints: **Registration:** ```csharp var builder = WebApplication.CreateBuilder(args); // Register CQRS services builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); // Add your commands and queries builder.Services.AddCommand(); builder.Services.AddQuery, PersonQueryHandler>(); var app = builder.Build(); // Map endpoints (this creates routes automatically) app.MapSvrntyCommands(); // Maps all commands to POST /api/command/{name} app.MapSvrntyQueries(); // Maps all queries to POST/GET /api/query/{name} app.Run(); ``` **How It Works:** 1. Extension methods iterate through `ICommandDiscovery` and `IQueryDiscovery` 2. For each command/query, creates Minimal API endpoints using `MapPost()`/`MapGet()` 3. Applies same naming conventions as MVC (lowerCamelCase) 4. Respects `[CommandControllerIgnore]` and `[QueryControllerIgnore]` attributes 5. Integrates with `ICommandAuthorizationService` and `IQueryAuthorizationService` 6. Supports OpenAPI/Swagger documentation **Features:** - Queries support both POST (with JSON body) and GET (with query string parameters) - Commands only support POST with JSON body - Authorization via authorization services (returns 401/403 status codes) - Customizable route prefixes: `MapSvrntyCommands("my-prefix")` - Automatic OpenAPI tags: "Commands" and "Queries" **Key Files:** - `Svrnty.CQRS.MinimalApi/EndpointRouteBuilderExtensions.cs` - Main implementation #### Option 2: MVC Controllers (Legacy) The AspNetCore packages use **dynamic controller generation** (maintained for backward compatibility): 1. **Feature Providers** (`CommandControllerFeatureProvider`, `QueryControllerFeatureProvider`): - Called during MVC startup - Discover handlers via `ICommandDiscovery`/`IQueryDiscovery` - Create generic controller types: `CommandController`, `QueryController` - Skip handlers marked with `[CommandControllerIgnore]` or `[QueryControllerIgnore]` 2. **Conventions** (`CommandControllerConvention`, `QueryControllerConvention`): - Apply naming to generated controllers - Default: Type name without suffix, converted to lowerCamelCase - Custom: Use `[CommandName]` or `[QueryName]` attribute 3. **Controllers** expose automatic endpoints: - Commands: `POST /api/command/{name}` - Queries: `POST /api/query/{name}` or `GET /api/query/{name}` **Registration Pattern:** ```csharp services .AddControllers() .AddSvrntyQueries() // Registers query feature provider .AddSvrntyCommands(); // Registers command feature provider ``` ### Dynamic Query System Dynamic queries provide OData-like filtering capabilities: **Core Components:** - `IDynamicQuery` - Interface with GetFilters(), GetSorts(), GetGroups(), GetAggregates() - `IQueryableProvider` - Provides base IQueryable to query against - `IAlterQueryableService` - Middleware to modify queries (e.g., security filters) - `DynamicQueryHandler` - Executes queries using PoweredSoft.DynamicQuery **Request Flow:** 1. HTTP request with filters/sorts/aggregates 2. DynamicQueryController receives request 3. DynamicQueryHandler gets base queryable from IQueryableProvider 4. Applies alterations from all registered IAlterQueryableService instances 5. Builds PoweredSoft query criteria 6. Executes and returns IQueryExecutionResult **Registration Example:** ```csharp services.AddDynamicQuery() .AddDynamicQueryWithProvider() .AddAlterQueryable(); ``` **Key Files:** - `Svrnty.CQRS.DynamicQuery/DynamicQueryHandler.cs` - `Svrnty.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs` - Request models ## Package Configuration All projects target .NET 10.0 and use C# 14, sharing common configuration: - **Target Framework**: `net10.0` (except DynamicQuery.Abstractions which multi-targets `netstandard2.1;net10.0`) - **Language Version**: C# 14 - **IsAotCompatible**: Currently set but not enforced (many dependencies are not AOT-compatible yet) - **Symbols**: Portable debug symbols with source, published as `.snupkg` - **NuGet metadata**: Icon, README, license (MIT), and repository URL included in packages - **Authors**: David Lebee, Mathias Beaulieu-Duncan - **Repository**: https://git.openharbor.io/Open-Harbor/dotnet-cqrs ### Package Dependencies - **Microsoft.Extensions.DependencyInjection.Abstractions**: 10.0.0-rc.2.25502.107 (will update to stable when .NET 10 is released) - **FluentValidation**: 11.11.0 - **Microsoft.CodeAnalysis.CSharp**: 4.13.0 - **PoweredSoft.DynamicQuery**: 3.0.1 - **Pluralize.NET**: 1.0.2 ## Publishing NuGet packages are published automatically via GitHub Actions when a release is created: **Workflow:** `.github/workflows/publish-nugets.yml` 1. Triggered on release publication 2. Extracts version from release tag 3. Runs `dotnet pack -c Release -p:Version={tag}` 4. Pushes to NuGet.org using `NUGET_API_KEY` secret **Manual publish:** ```bash # Create packages with specific version dotnet pack -c Release -o ./artifacts -p:Version=1.2.3 # Push to NuGet dotnet nuget push ./artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key YOUR_KEY ``` ## Development Workflow **Adding a New Command/Query Handler:** 1. Create command/query POCO in consumer project 2. Implement handler: `ICommandHandler` 3. Register in DI: `services.AddCommand()` 4. (Optional) Add validator: `services.AddTransient, Validator>()` 5. Controller endpoint is automatically generated **Adding a New Feature to Framework:** 1. Add interface to appropriate Abstractions project 2. Implement in corresponding implementation project 3. Update ServiceCollectionExtensions with registration method 4. Ensure all projects maintain AOT compatibility (unless AspNetCore-specific) 5. Update package version and release notes **Naming Conventions:** - Commands/Queries: Use `[CommandName]` or `[QueryName]` attribute for custom names - Default naming: Strips "Command"/"Query" suffix, converts to lowerCamelCase - Example: `CreatePersonCommand` -> `createPerson` endpoint ## C# 14 Language Features The project now uses C# 14, which introduces several new features. Be aware of these breaking changes: **Potential Breaking Changes:** - **`field` keyword**: New contextual keyword in property accessors for implicit backing fields - **`extension` keyword**: Reserved for extension containers; use `@extension` for identifiers - **`partial` return type**: Cannot use `partial` as return type without escaping - **Span overload resolution**: New implicit conversions may select different overloads - **`scoped` as lambda modifier**: Always treated as modifier in lambda parameters **New Features Available:** - Extension members (static extension members and extension properties) - Implicit span conversions - Unbound generic types with `nameof` - Lambda parameter modifiers without type specification - Partial instance constructors and events - Null-conditional assignment (`?.=` and `?[]=`) The codebase currently compiles without warnings on C# 14. ## Important Implementation Notes 1. **AOT Compatibility**: Currently not enforced. The `IsAotCompatible` property is set on some projects but many dependencies (including FluentValidation, PoweredSoft.DynamicQuery) are not AOT-compatible. Future work may address this. 2. **Async Everywhere**: All handlers are async. Always support CancellationToken. 3. **Generic Type Safety**: Framework relies heavily on generics for compile-time safety. When adding features, maintain strong typing. 4. **Metadata Pattern**: When extending discovery, always create corresponding metadata classes (implement ICommandMeta/IQueryMeta). 5. **Feature Provider Timing**: Controller generation happens during MVC startup. Discovery services must be registered before calling AddSvrntyCommands/Queries. 6. **FluentValidation**: This framework only REGISTERS validators. Actual validation execution requires separate middleware/filters in consumer applications. 7. **DynamicQuery Interceptors**: Support up to 5 interceptors per query type. Interceptors modify PoweredSoft DynamicQuery behavior. ## Common Code Locations - Handler interfaces: `Svrnty.CQRS.Abstractions/ICommandHandler.cs`, `IQueryHandler.cs` - Discovery implementations: `Svrnty.CQRS/Discovery/` - Service registration: `*/ServiceCollectionExtensions.cs` in each project - Controller templates: `Svrnty.CQRS.AspNetCore/Mvc/*Controller.cs` - Dynamic query logic: `Svrnty.CQRS.DynamicQuery/DynamicQueryHandler.cs` - Naming/routing: `Svrnty.CQRS.AspNetCore/Mvc/*Convention.cs`