-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Akash Goswami edited this page Jun 29, 2026
·
1 revision
Layered solution with strict one-way dependencies:
Api → Service → Data → Domain (Domain depends on nothing)
| Project | Responsibility |
|---|---|
| DocAnalytics.Domain | Entities + contracts (ITenantScoped, ICurrentUser). No dependencies. |
| DocAnalytics.Data |
AppDbContext, migrations, DbSeeder, AddPersistence(). References Domain. |
| DocAnalytics.Service | Business logic (IXxxService + XxxService), per-feature AddXxxFeature() DI. References Data + Domain. |
| DocAnalytics.Api | Controllers, middleware, JWT, Program.cs (composition root). References Service. |
Controllers never touch AppDbContext directly — they call a service. The service runs the EF query; the global query filter auto-applies tenant/site scoping. See Tenant and Site Isolation.
Each feature ships as its own files — IXxxService + XxxService + XxxController + XxxDtos + an AddXxxFeature() DI extension — so contributors don't edit shared files and merges stay clean.
| Layer | Choice |
|---|---|
| Backend | ASP.NET Core Web API (controller-based), .NET 10 |
| ORM | EF Core 10 (migration-based, parameterized LINQ) |
| Database | PostgreSQL 18 |
| Auth | JWT Bearer tokens |
| Password hashing | BCrypt.Net-Next |
| API docs | Swagger / OpenAPI (Swashbuckle) |
| Naming | C# PascalCase → DB snake_case (EFCore.NamingConventions) |
The original design docs said ".NET 8", but the project targets .NET 10 (
net10.0) — treat that as the source of truth.
-
EF Core 10.0.9 (
Microsoft.EntityFrameworkCore+.Relational) - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.2
- EFCore.NamingConventions 10.0.1
- Microsoft.AspNetCore.Authentication.JwtBearer 10.0.9
- Swashbuckle.AspNetCore 10.2.1 (Microsoft.OpenApi 2.x)
- BCrypt.Net-Next 4.2.0
- System.IdentityModel.Tokens.Jwt (Service project)
DocAnalytics.slnx
│
├─ DocAnalytics.Domain # entities + contracts (no dependencies)
├─ DocAnalytics.Data # AppDbContext, Migrations/, DbSeeder, AddPersistence()
├─ DocAnalytics.Service # business logic; per-feature folders + AddXxxFeature()
│ └─ Auth/ # AuthDtos, IAuthService/AuthService, JwtTokenService, AddAuthFeature
└─ DocAnalytics.Api # controllers, middleware, JWT, Program.cs
├─ Auth/ # JwtSettings
├─ Common/ # ApiResponse<T>, CurrentUser, BaseController
├─ Controllers/ # AuthController, SitesController, BatchesController, ...
├─ Extensions/ # ApiServiceExtensions (AddJwtAuth, AddSwaggerWithJwt, ...)
└─ Middleware/ # TenantSiteMiddleware
-
Response envelope:
ApiResponse<T> { Data, Meta, Error }with.Ok(...),.OkList(...),.Fail(...). -
Tenant context:
ICurrentUser { UserId, TenantId, SiteId, Role }— interface in Domain, concreteCurrentUserin Api, populated byTenantSiteMiddleware. -
JWT claims:
userId,tenantId,role(case-sensitive — middleware reads these exact names). -
Base route: everything under
/api/v1. -
Pagination: offset-based (
page+pageSize, max 100). -
Filtering/sorting: query-string params;
sortByvalidated against an allow-list (no string concatenation). -
DI: each feature exposes
AddXxxFeature()called fromProgram.cs.