Skip to content

Architecture

Akash Goswami edited this page Jun 29, 2026 · 1 revision

Architecture

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.

The golden rule

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.

Feature-slice pattern

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.

Tech stack

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.

Key package versions

  • 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)

Project structure

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

Conventions

  • Response envelope: ApiResponse<T> { Data, Meta, Error } with .Ok(...), .OkList(...), .Fail(...).
  • Tenant context: ICurrentUser { UserId, TenantId, SiteId, Role } — interface in Domain, concrete CurrentUser in Api, populated by TenantSiteMiddleware.
  • 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; sortBy validated against an allow-list (no string concatenation).
  • DI: each feature exposes AddXxxFeature() called from Program.cs.

Clone this wiki locally