# Best Practices Guidelines and patterns for building production-ready applications with Svrnty.CQRS. ## Overview This section provides best practices, design patterns, and recommendations for using Svrnty.CQRS effectively in production environments. ## Topics ### [Command Design](command-design.md) Design effective commands: - Single responsibility - Validation at boundaries - Immutability - Rich vs anemic models ### [Query Design](query-design.md) Optimize query performance: - Projection patterns - Database indexing - Caching strategies - Pagination ### [Event Design](event-design.md) Event versioning and schema evolution: - Naming conventions - Event versioning strategies - Backward compatibility - Upcasting patterns ### [Error Handling](error-handling.md) Handle errors gracefully: - Exception types and mapping - Validation vs business errors - Retry strategies - Dead letter queues ### [Security](security.md) Secure your application: - Authentication - Authorization services - Input validation - Rate limiting ### [Performance](performance.md) Optimize for performance: - Connection pooling - Batch operations - Async/await patterns - Database tuning ### [Testing](testing.md) Test your CQRS application: - Unit testing handlers - Integration testing - Event replay testing - Projection testing ### [Deployment](deployment.md) Deploy to production: - Configuration management - Database migrations - Health checks - Monitoring setup ### [Multi-Tenancy](multi-tenancy.md) Multi-tenant patterns: - Tenant isolation strategies - Database per tenant vs shared - Query filtering - Security considerations ## Quick Reference ### Command Best Practices ```csharp // ✅ Good public record CreateOrderCommand { [Required] public int CustomerId { get; init; } [Required, MinLength(1)] public List Items { get; init; } = new(); public decimal TotalAmount => Items.Sum(i => i.Price * i.Quantity); } // ❌ Bad public class CreateOrderCommand { public int CustomerId; // Not init-only public List Items; // Mutable, no validation // Missing calculated properties } ``` ### Query Best Practices ```csharp // ✅ Good - Projection with pagination public class ListOrdersQuery { public int Page { get; init; } = 1; public int PageSize { get; init; } = 20; } public async Task> HandleAsync(ListOrdersQuery query) { return await _context.Orders .AsNoTracking() .Skip((query.Page - 1) * query.PageSize) .Take(query.PageSize) .Select(o => new OrderSummary { ... }) .ToPagedResultAsync(); } // ❌ Bad - No projection, no pagination public async Task> HandleAsync() { return await _context.Orders.ToListAsync(); // Loads everything! } ``` ### Event Best Practices ```csharp // ✅ Good public record OrderPlacedEvent { public string EventId { get; init; } = Guid.NewGuid().ToString(); public int OrderId { get; init; } public DateTimeOffset PlacedAt { get; init; } public string CorrelationId { get; init; } = string.Empty; public int Version { get; init; } = 1; // Versioning from day 1 } // ❌ Bad public class OrderPlaced // Present tense { public int OrderId; // Mutable // No event ID, timestamp, or version } ``` ## Architecture Patterns ### Modular Solution Structure ``` Solution/ ├── MyApp.Api/ # HTTP/gRPC endpoints │ ├── Program.cs │ └── appsettings.json ├── MyApp.CQRS/ # Commands, queries, handlers │ ├── Commands/ │ ├── Queries/ │ └── Validators/ ├── MyApp.Domain/ # Domain models, events │ ├── Entities/ │ ├── Events/ │ └── ValueObjects/ └── MyApp.Infrastructure/ # Data access, external services ├── Repositories/ ├── DbContext/ └── ExternalServices/ ``` **Benefits:** - Clear separation of concerns - Testable in isolation - Easy to navigate - Supports team scaling ### CQRS + Event Sourcing ``` Command → Handler → Domain Model → Events → Event Store ↓ Projections → Read Models → Queries ``` ### Repository Pattern ```csharp public interface IOrderRepository { Task GetByIdAsync(int id); Task AddAsync(Order order); Task UpdateAsync(Order order); } // Use in command handlers public class CreateOrderHandler : ICommandHandler { private readonly IOrderRepository _repository; public async Task HandleAsync(CreateOrderCommand command, CancellationToken ct) { var order = Order.Create(command); await _repository.AddAsync(order); return order.Id; } } ``` ## Common Anti-Patterns ### ❌ Anemic Domain Models ```csharp // Bad - Logic in handler, not domain public class Order { public int Id { get; set; } public decimal Total { get; set; } public string Status { get; set; } } public class PlaceOrderHandler { public async Task HandleAsync(PlaceOrderCommand cmd) { var order = new Order { Total = cmd.Items.Sum(i => i.Price * i.Quantity), Status = "Placed" }; // All logic in handler! } } ``` **Better:** ```csharp // Good - Rich domain model public class Order { private readonly List _items = new(); public static Order Create(List items) { if (items.Count == 0) throw new InvalidOperationException("Order must have items"); var order = new Order(); order._items.AddRange(items); return order; } public decimal Total => _items.Sum(i => i.Price * i.Quantity); } ``` ### ❌ Querying Write Model ```csharp // Bad - Querying entity directly public async Task GetOrderAsync(int id) { return await _context.Orders .Include(o => o.Items) .Include(o => o.Customer) .FirstOrDefaultAsync(o => o.Id == id); } ``` **Better:** ```csharp // Good - Use projection/read model public async Task GetOrderAsync(int id) { return await _readContext.OrderSummaries .FirstOrDefaultAsync(o => o.OrderId == id); } ``` ### ❌ Missing Validation ```csharp // Bad - No validation public class CreateOrderHandler { public async Task HandleAsync(CreateOrderCommand cmd) { var order = new Order { CustomerId = cmd.CustomerId }; // What if CustomerId is invalid? await _repository.AddAsync(order); return order.Id; } } ``` **Better:** ```csharp // Good - FluentValidation public class CreateOrderCommandValidator : AbstractValidator { public CreateOrderCommandValidator() { RuleFor(x => x.CustomerId).GreaterThan(0); RuleFor(x => x.Items).NotEmpty(); } } ``` ## See Also - [Architecture Overview](../architecture/README.md) - [Event Streaming](../event-streaming/README.md) - [Observability](../observability/README.md) - [Troubleshooting](../troubleshooting/README.md)