7.8 KiB
AGENTS.md — Balinyaar Server
Agent-oriented guide to the backend. For human setup/run instructions see README.md.
Stack
- ASP.NET Core / .NET 10 (
net10.0), Web API - Clean Architecture (Domain → Application → Infrastructure → API)
- CQRS with MediatR (source-generated
Mediator) - EF Core 10 + SQL Server (Repository + Unit of Work)
- ASP.NET Core Identity with JWE (signed + AES-encrypted JWT), OTP, and dynamic permission authorization
- Mapster (mapping), FluentValidation (validation), Serilog (logging), OpenTelemetry + prometheus-net (observability), NSwag/Swagger (OpenAPI), Asp.Versioning (API versioning)
- xUnit + NSubstitute (tests)
- Centralized NuGet versions in
Directory.Packages.props
Commands (run from server/)
| Task | Command |
|---|---|
| Restore | dotnet restore CleanArcTemplate.sln |
| Build | dotnet build CleanArcTemplate.sln |
| Run API | dotnet run --project src/API/CleanArc.Web.Api/CleanArc.Web.Api.csproj |
| Test | dotnet test CleanArcTemplate.sln |
| Add migration | dotnet ef migrations add <Name> --project src/Infrastructure/CleanArc.Infrastructure.Persistence --startup-project src/API/CleanArc.Web.Api |
Startup project: src/API/CleanArc.Web.Api. Default URL https://localhost:5002, Swagger at /swagger. On boot the app applies EF migrations and seeds default users (Program.cs → ApplyMigrationsAsync() / SeedDefaultUsersAsync()), so a reachable DB is required.
Projects by layer
src/
├── Core/
│ ├── CleanArc.Domain Entities (User, Order, Role...), BaseEntity, IEntity, ITimeModification
│ └── CleanArc.Application CQRS Features/, Contracts/ (interfaces), Models/, MediatR pipeline (Common/)
├── Infrastructure/
│ ├── CleanArc.Infrastructure.Persistence ApplicationDbContext, Configuration/, Repositories/, Migrations/
│ ├── CleanArc.Infrastructure.Identity Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed), ServiceConfiguration/
│ ├── CleanArc.Infrastructure.CrossCutting Logging (Serilog)
│ └── CleanArc.Infrastructure.Monitoring HealthCheck / OpenTelemetry / Prometheus configs
├── API/
│ ├── CleanArc.Web.Api Program.cs, Controllers/V1/, appsettings*.json
│ ├── CleanArc.WebFramework BaseController, Filters/, Middlewares/, Swagger/, Attributes/
│ └── Plugins/CleanArc.Web.Plugins.Grpc GrpcPluginStartup, Services/, ProtoModels/
├── Shared/CleanArc.SharedKernel Extensions + validation base used by all layers
└── Tests/ CleanArc.Tests.Setup + CleanArc.Test.Infrastructure.Identity
Dependency direction points inward: Domain depends on nothing; Application depends on Domain; Infrastructure and API implement/consume Application's contracts. Never make Domain or Application reference Infrastructure or the API.
Startup wiring — src/API/CleanArc.Web.Api/Program.cs
Service registration is composed from per-layer extension methods (in each project's ServiceConfiguration):
ConfigureHealthChecks() · SetupOpenTelemetry()
AddApplicationServices() // MediatR + validators + pipeline behaviors
RegisterIdentityServices(...) // Identity, JWT/JWE, authorization policies
AddPersistenceServices(...) // DbContext, UnitOfWork, repositories
AddWebFrameworkServices() // API versioning
AddSwagger("v1","v1.1") · RegisterValidatorsAsServices() · AddMapster()
ConfigureGrpcPluginServices() // gRPC plugin
Pipeline order: exception handling → Swagger → routing → authentication → authorization → controllers → metrics → health checks → ConfigureGrpcPipeline().
When adding infrastructure, expose it as an extension method and call it here rather than inlining into Program.cs.
CQRS — how a feature is shaped
Features live under CleanArc.Application/Features/<Area>/{Commands|Queries}/<Name>/. A query example (Features/Order/Queries/GetAllOrders/):
GetAllOrdersQuery.cs—record ... : IRequest<OperationResult<...>>GetAllOrdersQueryHandler.cs—internalhandler; depends onIUnitOfWork,IMapper; returnsOperationResult<T>GetAllOrdersQueryResult.cs— the DTO returned
Commands additionally implement IValidatableModel<T> and declare FluentValidation rules; the ValidateCommandBehavior MediatR pipeline (Application/Common/) runs validators before the handler and surfaces errors in OperationResult.
To add a feature: create the folder with the request + handler (+ result/validator), then call it from a controller via _sender.Send(...). Contracts the handler needs go in Application/Contracts/ and are implemented in Infrastructure.
Controllers & results
- Controllers live in
CleanArc.Web.Api/Controllers/V1/and inheritBaseController(CleanArc.WebFramework/BaseController/BaseController.cs), which exposesUserId/UserName/etc. from claims and mapsOperationResult<T>→IActionResult. - All responses are wrapped in
OperationResult<T>(Application/Models/Common/):Result,IsSuccess,ErrorMessages,IsNotFound,IsException. Use the factory methods (SuccessResult,FailureResult,NotFoundResult). - Protected endpoints use
[Authorize(ConstantPolicies.DynamicPermission)].
Persistence
ApplicationDbContext(CleanArc.Infrastructure.Persistence/ApplicationDbContext.cs) extendsIdentityDbContext<...>; it auto-registersIEntitytypes and applies allIEntityTypeConfigurationfrom the assembly.- Per-entity config in
Configuration/<Area>Config/. Repositories inRepositories/derive fromBaseAsyncRepository<T>; expose them throughIUnitOfWork(interface inApplication/Contracts/Persistence/). Commit viaunitOfWork.CommitAsync(). - Migrations in
Migrations/. Add new ones with thedotnet efcommand above.
Identity & auth
- Token service:
CleanArc.Infrastructure.Identity/Jwt/JwtService.cs(IJwtService) — issues JWE (HMAC-SHA256 signed, AES-128 encrypted), refresh tokens, and OTP/phone-based tokens. - Custom Identity managers/stores under
Identity/Manager/andIdentity/Store/. - Dynamic permissions:
Identity/PermissionManager/(DynamicPermissionService,DynamicPermissionHandler,ConstantPolicies). - Settings from
appsettings.json→IdentitySettings(SecretKey,Encryptkey= 16 chars,Issuer,Audience, lifetimes).
gRPC plugin
Plugins/CleanArc.Web.Plugins.Grpc is a self-contained module mounted via Application Parts. GrpcPluginStartup.cs provides ConfigureGrpcPluginServices() / ConfigureGrpcPipeline() (called from Program.cs). Proto contracts in ProtoModels/*.proto, services in Services/. The host uses HTTP/2 (Kestrel config) for gRPC.
Conventions
- Add cross-layer wiring as
ServiceConfigurationextension methods, not inline inProgram.cs. - Keep handlers
internal; returnOperationResult<T>; don't throw for expected failures (useFailureResult/NotFoundResult). - Use Mapster for entity↔DTO mapping; FluentValidation for input validation.
- Centralize package versions in
Directory.Packages.props(no inlineVersion=in.csproj). - The
CleanArc*namespace/.slnnaming is internal project naming, not template branding — don't rename it without an explicit request (it touches every file and the EF migrations).