Files
baya-monorepo/dev/phases/_shared/backend-conventions-checklist.md
T
2026-06-28 21:59:59 +03:30

4.1 KiB

Backend conventions checklist (quick reference)

The authoritative rules are server/CLAUDE.md and 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/<Area>/{Commands|Queries}/<Name>/.
  • 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<T> per entity in Persistence/Configuration/<Area>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.