dotnet-cqrs/docs/architecture
2025-12-11 01:18:24 -05:00
..
cqrs-pattern.md this is a mess 2025-12-11 01:18:24 -05:00
dependency-injection.md this is a mess 2025-12-11 01:18:24 -05:00
extensibility-points.md this is a mess 2025-12-11 01:18:24 -05:00
metadata-discovery.md this is a mess 2025-12-11 01:18:24 -05:00
modular-solution-structure.md this is a mess 2025-12-11 01:18:24 -05:00
README.md this is a mess 2025-12-11 01:18:24 -05:00

Architecture

Understand the design principles, patterns, and extensibility of Svrnty.CQRS.

Overview

Svrnty.CQRS is built on solid architectural principles that make it:

  • Metadata-driven - Runtime discovery through compile-time registration
  • Modular - Clear separation between abstractions and implementations
  • Extensible - Multiple extension points for customization
  • Convention-based - Minimal configuration with sensible defaults
  • Type-safe - Compile-time type checking with runtime flexibility

Architecture Topics

CQRS Pattern

Deep dive into the Command Query Responsibility Segregation pattern:

  • Separation of reads and writes
  • Benefits and trade-offs
  • When to use CQRS
  • Common anti-patterns
  • Implementation patterns

Metadata Discovery

How Svrnty.CQRS uses metadata for automatic endpoint generation:

  • Metadata registration pattern
  • Discovery services (ICommandDiscovery, IQueryDiscovery)
  • Runtime enumeration
  • Endpoint generation process
  • Type safety with generics

Modular Solution Structure

Best practices for organizing your solution into layers:

  • Multi-project solution structure
  • Api → CQRS → Domain → DAL dependencies
  • Separation of concerns
  • Project references
  • Real-world example

Dependency Injection

DI patterns and handler registration:

  • Service registration patterns
  • Handler lifetime management
  • Scoped vs Transient vs Singleton
  • Constructor injection
  • Service resolution

Extensibility Points

Framework extension mechanisms:

  • Custom authorization services
  • Query alteration services
  • Dynamic query interceptors
  • Custom attributes
  • Middleware integration

Key Architectural Concepts

1. Abstractions vs Implementations

Svrnty.CQRS separates interfaces from implementations:

Svrnty.CQRS.Abstractions (interfaces only)
    ↓ depends on
Svrnty.CQRS (core implementation)
    ↓ depends on
Svrnty.CQRS.MinimalApi (HTTP integration)
Svrnty.CQRS.Grpc (gRPC integration)

Benefits:

  • Consumer projects reference only abstractions
  • Minimal dependencies
  • Easy to swap implementations
  • Clear contracts

2. Metadata-Driven Discovery

Instead of scanning assemblies at runtime, Svrnty.CQRS uses explicit metadata:

// Registration creates metadata
services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();

// Metadata stored as singleton
services.AddSingleton<ICommandMeta>(new CommandMeta<CreateUserCommand, int, CreateUserCommandHandler>());

// Discovery queries metadata
public class CommandDiscovery : ICommandDiscovery
{
    private readonly IEnumerable<ICommandMeta> _metas;

    public IEnumerable<ICommandMeta> GetCommands() => _metas;
}

Benefits:

  • No reflection-heavy assembly scanning
  • Faster startup
  • AOT-compatible
  • Explicit control

3. Convention Over Configuration

Minimal configuration with smart defaults:

// Default naming convention
CreateUserCommand  POST /api/command/createUser

// Custom naming
[CommandName("register")]
CreateUserCommand  POST /api/command/register

// Default route prefix
/api/command/* and /api/query/*

4. Type Safety

Compile-time type safety with generic constraints:

// Type-safe registration
services.AddCommand<TCommand, TResult, THandler>()
    where THandler : ICommandHandler<TCommand, TResult>;

// Compile error if types don't match
services.AddCommand<CreateUserCommand, int, WrongHandler>(); // ❌ Compile error

Architectural Layers

Typical Application Structure

┌─────────────────────────────────────────┐
│         Presentation Layer              │
│  (HTTP Endpoints, gRPC Services)        │
│  - Svrnty.CQRS.MinimalApi               │
│  - Svrnty.CQRS.Grpc                     │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│         Application Layer               │
│  (Commands, Queries, Handlers)          │
│  - Command/Query definitions            │
│  - Handler implementations              │
│  - Validators                           │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│           Domain Layer                  │
│  (Business logic, Entities, Events)     │
│  - Domain models                        │
│  - Business rules                       │
│  - Domain events                        │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│      Infrastructure Layer               │
│  (Data access, External services)       │
│  - Repositories                         │
│  - Database context                     │
│  - External API clients                 │
└─────────────────────────────────────────┘

Multi-Project Solution

For larger applications, use multiple projects:

MySolution/
├── MySolution.Api/              # HTTP/gRPC endpoints
├── MySolution.CQRS/             # Commands, queries, handlers
├── MySolution.Domain/           # Domain models, events
├── MySolution.Infrastructure/   # EF Core, repositories
└── MySolution.Tests/            # Unit and integration tests

Design Patterns Used

1. Command Pattern

Commands encapsulate requests as objects:

// Command (request object)
public record CreateUserCommand
{
    public string Name { get; init; } = string.Empty;
}

// Handler (executes the request)
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
{
    public Task<int> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken)
    {
        // Execute logic
    }
}

2. Mediator Pattern

CQRS acts as a mediator between API and business logic:

Client → Endpoint → Handler → Business Logic

No direct dependencies between client and business logic.

3. Strategy Pattern

Multiple implementations of same interface:

ICommandHandler<CreateUserCommand, int>
     CreateUserCommandHandler
     CreateUserWithEmailCommandHandler
     CreateUserWithSSOCommandHandler

4. Decorator Pattern

Validators, authorization, logging wrap handlers:

Client → Validation → Authorization → Handler → Business Logic

Extensibility Architecture

Extension Points

  1. Authorization

    • ICommandAuthorizationService<TCommand>
    • IQueryAuthorizationService<TQuery>
  2. Query Alteration

    • IAlterQueryableService<TSource, TDestination>
  3. Dynamic Query Interceptors

    • IDynamicQueryInterceptorProvider
  4. Attributes

    • [CommandName], [QueryName]
    • [IgnoreCommand], [IgnoreQuery]
  5. Middleware

    • ASP.NET Core pipeline integration
    • Custom filters

Performance Considerations

Startup Performance

  • Fast startup - Metadata pattern avoids assembly scanning
  • Minimal reflection - Type information captured at registration
  • AOT-friendly - No runtime type discovery

Runtime Performance

  • Direct handler invocation - No mediator overhead
  • DI container resolution - Standard ASP.NET Core performance
  • Endpoint routing - Uses built-in routing (HTTP) or gRPC runtime

Memory Efficiency

  • Singleton metadata - One instance per command/query type
  • Scoped handlers - Created per request, disposed after
  • No caching layer - Direct execution

Security Architecture

Defense in Depth

1. Network Layer (HTTPS, firewall)
2. Authentication (JWT, API keys)
3. Authorization (IAuthorizationService)
4. Validation (FluentValidation)
5. Business Rules (in handlers)
6. Data Access (parameterized queries)

Built-in Security Features

  • Validation before execution (FluentValidation)
  • Authorization services (per command/query)
  • Attribute-based endpoint control ([Ignore])
  • Integration with ASP.NET Core auth

What's Next?

Explore specific architectural topics:

See Also