# Core Features Master the fundamental features of Svrnty.CQRS: commands, queries, validation, and dynamic queries. ## Overview Svrnty.CQRS provides four core feature sets: - ✅ **Commands** - Write operations that change system state - ✅ **Queries** - Read operations that retrieve data - ✅ **Validation** - Input validation with FluentValidation - ✅ **Dynamic Queries** - OData-like filtering, sorting, and aggregation ## Feature Categories ### [Commands](commands/README.md) Commands represent write operations: - [Commands Overview](commands/README.md) - Introduction to commands - [Basic Commands](commands/basic-commands.md) - Commands without results - [Commands with Results](commands/commands-with-results.md) - Commands that return values - [Command Registration](commands/command-registration.md) - Registration patterns - [Command Authorization](commands/command-authorization.md) - ICommandAuthorizationService - [Command Attributes](commands/command-attributes.md) - [IgnoreCommand], [CommandName], etc. **Quick example:** ```csharp public record CreateUserCommand { public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; } public class CreateUserCommandHandler : ICommandHandler { public async Task HandleAsync(CreateUserCommand command, CancellationToken cancellationToken) { // Create user logic return userId; } } // Registration builder.Services.AddCommand(); // Endpoint: POST /api/command/createUser ``` ### [Queries](queries/README.md) Queries represent read operations: - [Queries Overview](queries/README.md) - Introduction to queries - [Basic Queries](queries/basic-queries.md) - Simple query handlers - [Query Registration](queries/query-registration.md) - Registration patterns - [Query Authorization](queries/query-authorization.md) - IQueryAuthorizationService - [Query Attributes](queries/query-attributes.md) - [IgnoreQuery], [QueryName], etc. **Quick example:** ```csharp public record GetUserQuery { public int UserId { get; init; } } public class GetUserQueryHandler : IQueryHandler { public async Task HandleAsync(GetUserQuery query, CancellationToken cancellationToken) { // Fetch user data return userDto; } } // Registration builder.Services.AddQuery(); // Endpoints: // GET /api/query/getUser?userId=1 // POST /api/query/getUser ``` ### [Validation](validation/README.md) Input validation with FluentValidation: - [Validation Overview](validation/README.md) - Introduction to validation - [FluentValidation Setup](validation/fluentvalidation-setup.md) - Setting up validators - [HTTP Validation](validation/http-validation.md) - RFC 7807 Problem Details - [gRPC Validation](validation/grpc-validation.md) - Google Rich Error Model - [Custom Validation](validation/custom-validation.md) - Custom validation scenarios **Quick example:** ```csharp public class CreateUserCommandValidator : AbstractValidator { public CreateUserCommandValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Email).NotEmpty().EmailAddress(); } } // Registration with validator builder.Services.AddCommand(); ``` ### [Dynamic Queries](dynamic-queries/README.md) OData-like querying capabilities: - [Dynamic Queries Overview](dynamic-queries/README.md) - Introduction to dynamic queries - [Getting Started](dynamic-queries/getting-started.md) - First dynamic query - [Filters and Sorts](dynamic-queries/filters-and-sorts.md) - Filtering, sorting, paging - [Groups and Aggregates](dynamic-queries/groups-and-aggregates.md) - Grouping and aggregation - [Queryable Providers](dynamic-queries/queryable-providers.md) - IQueryableProvider implementation - [Alter Queryable Services](dynamic-queries/alter-queryable-services.md) - Security filters, tenant isolation - [Interceptors](dynamic-queries/interceptors.md) - IDynamicQueryInterceptorProvider **Quick example:** ```csharp // Provider public class UserQueryableProvider : IQueryableProvider { private readonly ApplicationDbContext _context; public Task> GetQueryableAsync(object query, CancellationToken cancellationToken) { return Task.FromResult(_context.Users.AsQueryable()); } } // Registration builder.Services.AddDynamicQueryWithProvider(); // Endpoint: POST /api/query/users // Request body: { "filters": [{ "path": "Name", "type": 2, "value": "Alice" }], "sorts": [{ "path": "Email", "ascending": true }], "page": 1, "pageSize": 10 } ``` ## Feature Comparison | Feature | Commands | Queries | Dynamic Queries | |---------|----------|---------|-----------------| | **Purpose** | Write data | Read data | Advanced read with filters | | **Returns data** | Optional | Always | Always | | **HTTP methods** | POST only | GET or POST | GET or POST | | **Caching** | No | Yes | Yes | | **Side effects** | Yes | No | No | | **Validation** | Yes | Yes | Yes | | **Filtering** | N/A | Manual | Automatic | | **Sorting** | N/A | Manual | Automatic | | **Paging** | N/A | Manual | Automatic | ## Core Interfaces ### Command Interfaces ```csharp // Command without result public interface ICommandHandler { Task HandleAsync(TCommand command, CancellationToken cancellationToken = default); } // Command with result public interface ICommandHandler { Task HandleAsync(TCommand command, CancellationToken cancellationToken = default); } ``` ### Query Interface ```csharp // Query always returns result public interface IQueryHandler { Task HandleAsync(TQuery query, CancellationToken cancellationToken = default); } ``` ### Dynamic Query Interfaces ```csharp // Dynamic query interface public interface IDynamicQuery { List GetFilters(); List GetSorts(); List GetGroups(); List GetAggregates(); int? Page { get; } int? PageSize { get; } } // Queryable provider public interface IQueryableProvider { Task> GetQueryableAsync(object query, CancellationToken cancellationToken = default); } ``` ## Quick Start Examples ### Simple CRUD Operations ```csharp // Create public record CreateProductCommand { public string Name { get; init; } = string.Empty; public decimal Price { get; init; } } public class CreateProductCommandHandler : ICommandHandler { private readonly ApplicationDbContext _context; public async Task HandleAsync(CreateProductCommand command, CancellationToken cancellationToken) { var product = new Product { Name = command.Name, Price = command.Price }; _context.Products.Add(product); await _context.SaveChangesAsync(cancellationToken); return product.Id; } } // Read public record GetProductQuery { public int ProductId { get; init; } } public class GetProductQueryHandler : IQueryHandler { private readonly ApplicationDbContext _context; public async Task HandleAsync(GetProductQuery query, CancellationToken cancellationToken) { var product = await _context.Products.FindAsync(new object[] { query.ProductId }, cancellationToken); return new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price }; } } // Update public record UpdateProductCommand { public int ProductId { get; init; } public string Name { get; init; } = string.Empty; public decimal Price { get; init; } } public class UpdateProductCommandHandler : ICommandHandler { private readonly ApplicationDbContext _context; public async Task HandleAsync(UpdateProductCommand command, CancellationToken cancellationToken) { var product = await _context.Products.FindAsync(new object[] { command.ProductId }, cancellationToken); product.Name = command.Name; product.Price = command.Price; await _context.SaveChangesAsync(cancellationToken); } } // Delete public record DeleteProductCommand { public int ProductId { get; init; } } public class DeleteProductCommandHandler : ICommandHandler { private readonly ApplicationDbContext _context; public async Task HandleAsync(DeleteProductCommand command, CancellationToken cancellationToken) { var product = await _context.Products.FindAsync(new object[] { command.ProductId }, cancellationToken); _context.Products.Remove(product); await _context.SaveChangesAsync(cancellationToken); } } // Registration builder.Services.AddCommand(); builder.Services.AddQuery(); builder.Services.AddCommand(); builder.Services.AddCommand(); ``` ## Common Patterns ### Pattern 1: Command with Validation ```csharp // Command public record CreateUserCommand { public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; public int Age { get; init; } } // Validator public class CreateUserCommandValidator : AbstractValidator { public CreateUserCommandValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Email).NotEmpty().EmailAddress(); RuleFor(x => x.Age).GreaterThan(0).LessThanOrEqualTo(120); } } // Handler public class CreateUserCommandHandler : ICommandHandler { public async Task HandleAsync(CreateUserCommand command, CancellationToken cancellationToken) { // Validation already ran before handler execution // Business logic here } } ``` ### Pattern 2: Query with Authorization ```csharp // Query public record GetUserQuery { public int UserId { get; init; } } // Authorization public class GetUserAuthorizationService : IQueryAuthorizationService { public async Task CanExecuteAsync(GetUserQuery query, ClaimsPrincipal user, CancellationToken cancellationToken) { // Users can only view their own data (or admins) if (user.IsInRole("Admin")) return true; var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; return query.UserId.ToString() == userId; } } // Handler public class GetUserQueryHandler : IQueryHandler { public async Task HandleAsync(GetUserQuery query, CancellationToken cancellationToken) { // Authorization already checked before handler execution // Fetch logic here } } ``` ### Pattern 3: Dynamic Query with Security Filter ```csharp // Queryable provider public class OrderQueryableProvider : IQueryableProvider { private readonly ApplicationDbContext _context; public Task> GetQueryableAsync(object query, CancellationToken cancellationToken) { return Task.FromResult(_context.Orders.AsQueryable()); } } // Security filter public class OrderSecurityFilter : IAlterQueryableService { public IQueryable AlterQueryable(IQueryable queryable, object query, ClaimsPrincipal user) { // Non-admins can only see their own orders if (user.IsInRole("Admin")) return queryable; var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; return queryable.Where(o => o.UserId.ToString() == userId); } } // Registration builder.Services.AddDynamicQueryWithProvider(); builder.Services.AddScoped, OrderSecurityFilter>(); ``` ## What's Next? Dive deep into each feature: 1. **[Commands](commands/README.md)** - Master write operations 2. **[Queries](queries/README.md)** - Master read operations 3. **[Validation](validation/README.md)** - Add input validation 4. **[Dynamic Queries](dynamic-queries/README.md)** - Advanced querying ## See Also - [Getting Started](../getting-started/README.md) - Build your first application - [Architecture](../architecture/README.md) - Understanding the framework design - [Best Practices](../best-practices/README.md) - Production-ready patterns - [Tutorials](../tutorials/README.md) - Comprehensive examples