backend phase 0: foundation, cross-cutting seams & starter cleanup
Remove the Order demo (entity/feature/repo/config/gRPC/proto) and the three pre-marketplace migrations; regenerate a fresh InitialBaseline migration. Stand up the REST surface (PingController + System/Ping CQRS) proving the Mediator -> behaviors -> OperationResult -> ApiResult envelope end to end. Close wiring gaps: register LoggingBehavior (outermost) and add the built-in rate limiter (per-IP global + otp/auth/sensitive policies), placed before authentication. Add current-user + audit plumbing: ICurrentUser (HttpContext + null impls), rename BaseEntity audit fields to CreatedAt/ModifiedAt (DateTimeOffset) + CreatedById/ModifiedById, stamped by a new AuditFieldInterceptor. Introduce five cross-cutting seams (IDateTimeProvider, IFieldEncryptor, ICacheService, IObjectStorage, INotificationDispatcher) with in-memory/local mocks registered via AddCrossCuttingSeams. Add Baya.Test.Foundation (encryptor, audit interceptor, ping handler) and update docs, contracts (swagger.v1.json), handoff, report, and mocks registry.
This commit is contained in:
+39
-23
@@ -81,27 +81,36 @@ projects/assemblies, Clean-Architecture layers, and cross-layer dependencies.
|
||||
```
|
||||
src/
|
||||
├── Core/
|
||||
│ ├── Baya.Domain Entities (User, Order, Role…), BaseEntity, IEntity, ITimeModification
|
||||
│ └── Baya.Application Features/ (Commands & Queries), Contracts/, Models/, pipeline behaviors (Common/)
|
||||
│ ├── Baya.Domain Entities (User, Role…), BaseEntity, IEntity, ITimeModification, IAuditableEntity
|
||||
│ └── Baya.Application Features/ (Commands & Queries), Contracts/ (incl. Contracts/Common cross-cutting seams), Models/, pipeline behaviors (Common/)
|
||||
├── Infrastructure/
|
||||
│ ├── Baya.Infrastructure.Persistence ApplicationDbContext, Repositories/, Configuration/, Migrations/
|
||||
│ ├── Baya.Infrastructure.Identity Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed)
|
||||
│ ├── Baya.Infrastructure.CrossCutting Serilog wiring
|
||||
│ ├── Baya.Infrastructure.Persistence ApplicationDbContext, Repositories/, Configuration/, Migrations/, Interceptors/ (AuditFieldInterceptor)
|
||||
│ ├── Baya.Infrastructure.Identity Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed, CurrentUser/)
|
||||
│ ├── Baya.Infrastructure.CrossCutting Serilog wiring + Seams/ (mock impls of the cross-cutting seams) + AddCrossCuttingSeams
|
||||
│ └── Baya.Infrastructure.Monitoring HealthChecks, OpenTelemetry, prometheus-net
|
||||
├── API/
|
||||
│ ├── Baya.Web.Api Program.cs, Controllers/V1/, appsettings*.json
|
||||
│ ├── Baya.WebFramework BaseController, Filters/, Middlewares/, Swagger/, Routing/
|
||||
│ └── Plugins/Baya.Web.Plugins.Grpc gRPC services + .proto models
|
||||
│ ├── Baya.Web.Api Program.cs, Controllers/V1/ (PingController), appsettings*.json
|
||||
│ ├── Baya.WebFramework BaseController, Filters/, Middlewares/, Swagger/, Routing/, ServiceConfiguration/ (rate limiting)
|
||||
│ └── Plugins/Baya.Web.Plugins.Grpc gRPC services + .proto models (User only)
|
||||
├── Shared/Baya.SharedKernel Extensions + validation base
|
||||
└── Tests/
|
||||
├── Baya.Tests.Setup Shared test infrastructure (SQLite, NSubstitute setup)
|
||||
└── Baya.Test.Infrastructure.Identity xUnit identity tests
|
||||
├── Baya.Test.Infrastructure.Identity xUnit identity tests
|
||||
└── Baya.Test.Foundation xUnit tests for cross-cutting plumbing (encryptor, audit interceptor, ping)
|
||||
```
|
||||
|
||||
**Dependency direction points inward.** Domain has no dependencies. Application depends only on
|
||||
Domain. Infrastructure and API implement/consume Application contracts. Never make Domain or
|
||||
Application reference Infrastructure or the API — this is a hard rule.
|
||||
|
||||
**Cross-cutting seams.** Application defines mock-able external dependencies as interfaces in
|
||||
`Contracts/Common/` (`IDateTimeProvider`, `IFieldEncryptor`, `ICacheService`, `IObjectStorage`,
|
||||
`INotificationDispatcher`, plus `ICurrentUser`). Their in-memory/local mock implementations live in
|
||||
`Baya.Infrastructure.CrossCutting/Seams/` and are registered by `AddCrossCuttingSeams(configuration)`
|
||||
(config section `Seams`); `ICurrentUser` is registered in the Identity layer. Swapping a mock for a
|
||||
real provider is a registration change — handlers depend only on the contract. Audit fields are
|
||||
stamped by `AuditFieldInterceptor` (Persistence), not in handlers.
|
||||
|
||||
**Keeping the Project map current.** When a change touches the architecture — adds, removes, or
|
||||
renames a project/assembly, a Clean-Architecture layer, or a major folder, or changes a cross-layer
|
||||
dependency — you **must** update this Project map (and the dependency rule above, if affected) in the
|
||||
@@ -116,16 +125,20 @@ Service registration is composed from per-layer extension methods (each project'
|
||||
|
||||
```
|
||||
ConfigureHealthChecks() · SetupOpenTelemetry()
|
||||
AddApplicationServices() // Mediator + validators + pipeline behaviors
|
||||
RegisterIdentityServices(...) // Identity, JWT/JWE, authorization policies
|
||||
AddPersistenceServices(...) // DbContext, UnitOfWork, repositories
|
||||
AddApplicationServices() // Mediator + pipeline behaviors (Logging → Metrics → Validate)
|
||||
RegisterIdentityServices(...) // Identity, JWT/JWE, authorization policies, ICurrentUser + IHttpContextAccessor
|
||||
AddPersistenceServices(...) // DbContext (+ AuditFieldInterceptor), UnitOfWork, repositories
|
||||
AddCrossCuttingSeams(config) // IDateTimeProvider, IFieldEncryptor, ICacheService, IObjectStorage, INotificationDispatcher (mocks)
|
||||
AddWebFrameworkServices() // API versioning + snake_case routing
|
||||
AddRateLimitingPolicies() // built-in rate limiter: per-IP global + named (otp/auth/sensitive)
|
||||
AddSwagger("v1", "v1.1") · RegisterValidatorsAsServices() · AddMapster()
|
||||
ConfigureGrpcPluginServices()
|
||||
```
|
||||
|
||||
Pipeline order: exception handler → Swagger → routing → **authentication → authorization** →
|
||||
controllers → metrics → health checks → gRPC.
|
||||
Pipeline order: exception handler → Swagger → routing → **rate limiter → authentication →
|
||||
authorization** → controllers → metrics → health checks → gRPC. `UseRateLimiter()` is placed
|
||||
**before** `UseAuthentication()` so over-limit auth/OTP attempts are rejected (`429`) before hitting
|
||||
the auth stack.
|
||||
|
||||
When adding new infrastructure, expose it as an extension method and call it from `Program.cs` —
|
||||
never inline registrations there directly.
|
||||
@@ -137,17 +150,20 @@ never inline registrations there directly.
|
||||
Features live under `Baya.Application/Features/<Area>/{Commands|Queries}/<Name>/`:
|
||||
|
||||
```
|
||||
Features/Order/
|
||||
├── Commands/CreateOrderCommand/
|
||||
│ ├── CreateOrderCommand.cs record : ICommand<OperationResult<T>>
|
||||
│ ├── CreateOrderCommand.Handler.cs internal sealed class : ICommandHandler<...>
|
||||
│ └── CreateOrderCommand.Validator.cs
|
||||
└── Queries/GetUserOrdersQuery/
|
||||
├── GetUserOrdersQuery.cs
|
||||
├── GetUserOrdersQuery.Handler.cs
|
||||
└── GetUserOrdersQuery.Result.cs
|
||||
Features/<Area>/
|
||||
├── Commands/<VerbNoun>Command/
|
||||
│ ├── <VerbNoun>Command.cs record : IRequest<OperationResult<T>>
|
||||
│ ├── <VerbNoun>Command.Handler.cs internal sealed class : IRequestHandler<...>
|
||||
│ └── <VerbNoun>Command.Validator.cs
|
||||
└── Queries/<VerbNoun>Query/
|
||||
├── <VerbNoun>Query.cs
|
||||
├── <VerbNoun>Query.Handler.cs
|
||||
└── <VerbNoun>Query.Result.cs
|
||||
```
|
||||
|
||||
A minimal live example shipped in backend-phase-0: `Features/System/Queries/Ping/` (query + handler +
|
||||
result), surfaced by `Controllers/V1/PingController`.
|
||||
|
||||
Handlers are `internal sealed`. Requests are `record` types. Validators use FluentValidation and are
|
||||
picked up automatically by the `ValidateCommandBehavior` pipeline behavior. Never throw for expected
|
||||
failures — use `OperationResult` factory methods.
|
||||
|
||||
Reference in New Issue
Block a user