dotnet-cqrs/docs/ARCHITECTURE.md
2026-03-08 14:02:19 -04:00

6.0 KiB

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.