179 lines
6.0 KiB
Markdown
179 lines
6.0 KiB
Markdown
# 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.
|