# Backend conventions checklist (quick reference) The authoritative rules are [`server/CLAUDE.md`](../../../server/CLAUDE.md) and [`server/CONVENTIONS.md`](../../../server/CONVENTIONS.md). This is a tick-list to keep handy while you build. If anything here seems to conflict with those files, **those files win**. ## Architecture - [ ] Clean Architecture respected: Domain → Application → Infrastructure → API; **dependencies point inward**. Domain references nothing; Application references only Domain; Infrastructure/API implement Application contracts. Never reference Infrastructure/API from Domain/Application. - [ ] New cross-layer dependency or project/folder ⇒ update the **Project map** in `server/CLAUDE.md`. - [ ] New infrastructure is registered via a `ServiceConfiguration/` extension method called from `Program.cs` — never inline in `Program.cs`. ## CQRS / features - [ ] Feature lives in `Baya.Application/Features//{Commands|Queries}//`. - [ ] Requests are `record`; handlers are `internal sealed`; one handler per request. - [ ] **Never throw for expected failures** — return `OperationResult.SuccessResult/FailureResult/NotFoundResult`. - [ ] Contracts the handler needs are interfaces in `Application/Contracts/`, implemented in Infrastructure. - [ ] Input-bearing commands have a FluentValidation validator (picked up by `ValidateCommandBehavior`). ## Persistence (EF Core) - [ ] Read queries use `AsNoTracking()` **and** project with `.Select(...)` to a DTO — never hydrate entities to map them. Mapping (Mapster) happens in the handler after the query. - [ ] Every unbounded list is **paginated** (`Skip`/`Take`); no unbounded `ToListAsync()`. - [ ] Access the DB via `IUnitOfWork` in handlers; commit once per command (`CommitAsync`). - [ ] One `IEntityTypeConfiguration` per entity in `Persistence/Configuration/Config/`. - [ ] Soft-deletable entities declare a global query filter (`!IsDeleted` / `deleted_at IS NULL`). - [ ] Audit fields (`CreatedAt/ModifiedAt/CreatedById/ModifiedById`) are set by the SaveChanges interceptor via `ICurrentUser` — handlers don't pass them. - [ ] Encrypted PII columns (phone, national_id, IBAN, addresses, clinical notes) go through the field encryptor seam — never stored or logged in plaintext. ## Performance, caching, money, idempotency - [ ] Read-heavy/config/reference data is cached behind the cache seam with sensible invalidation; `platform_configs` values are read through the typed config accessor (cached), not hardcoded. - [ ] **Money is IRR `BIGINT` — no floats, anywhere.** Toman conversion happens only inside a provider adapter at its boundary. The three booking amounts always satisfy `gross = commission + payout`. - [ ] Money-path writes are **idempotent** (webhook dedup on the unique external-event key; filtered unique on succeeded transaction; forward-only state machines) and, where the phase says so, guarded by a Redis **distributed lock** — with the DB constraint as the authoritative backstop. - [ ] `ledger_entries` is append-only and balanced (Σdebit = Σcredit per `transaction_group_id`). ## API surface - [ ] Controllers are `sealed`, inherit `BaseController`, inject `ISender`, return `base.OperationResult(...)` — never `Ok()/BadRequest()/NotFound()` directly. (Note: the baseline has **no** controllers yet — REST is introduced in an early phase; follow that pattern thereafter.) - [ ] Routes use `[controller]`/`[action]` tokens (snake_case transformer); pass `CancellationToken`. - [ ] Authorize with the narrowest fitting policy; auth/OTP/refund/payout-sensitive endpoints are rate-limited. - [ ] The endpoint's contract is published to `dev/contracts/` (see operating-rules §6). ## Quality - [ ] `async`/`await` all the way; `CancellationToken` threaded through; no `.Result`/`.Wait()`/`async void`. - [ ] Package versions only in `Directory.Packages.props`. - [ ] Handler unit tests (NSubstitute) + at least one `WebApplicationFactory` integration test per new feature area (happy path, 401, validation 400). - [ ] `dotnet build` zero new warnings; `dotnet test` green; no dead code.