4.1 KiB
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 fromProgram.cs— never inline inProgram.cs.
CQRS / features
- Feature lives in
Baya.Application/Features/<Area>/{Commands|Queries}/<Name>/. - Requests are
record; handlers areinternal 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 unboundedToListAsync(). - Access the DB via
IUnitOfWorkin handlers; commit once per command (CommitAsync). - One
IEntityTypeConfiguration<T>per entity inPersistence/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 viaICurrentUser— 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_configsvalues 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 satisfygross = 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_entriesis append-only and balanced (Σdebit = Σcredit pertransaction_group_id).
API surface
- Controllers are
sealed, inheritBaseController, injectISender, returnbase.OperationResult(...)— neverOk()/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); passCancellationToken. - 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/awaitall the way;CancellationTokenthreaded through; no.Result/.Wait()/async void.- Package versions only in
Directory.Packages.props. - Handler unit tests (NSubstitute) + at least one
WebApplicationFactoryintegration test per new feature area (happy path, 401, validation 400). dotnet buildzero new warnings;dotnet testgreen; no dead code.