Coding standards
Coding standards
Section titled “Coding standards”Audience: backend
Status: specced
Owns: backend
Depends on: Backend stack ADR, System architecture
One standard across Orchestrator, CAP, LMS, and WorkMasters. Lint/format/type-check are CI-gated (merge blocked on failure), not optional warnings.
Folder layout
Section titled “Folder layout”src/ routes/ # thin — wire HTTP to controllers controllers/ # parse/validate request, call service, shape response services/ # business logic; orchestrates repositories + events repositories/ # Prisma access only — no business logic events/ emitters/ # internal (Node EventEmitter) domain events publishers/ # outbox → RabbitMQ publisher consumers/ # RabbitMQ consumers grpc/ server/ # handlers this service exposes clients/ # typed clients for calling other services jobs/ # scheduled/cron work (e.g. OTP expiry, SLA gauges) lib/ # shared internal utilities for this serviceShared packages
Section titled “Shared packages”| Package | Purpose |
|---|---|
@yourorg/common |
Logger, AppError hierarchy (incl. ScopeError), response envelope, cursor pagination, ULID, OTel bootstrap, outbox/event helpers, RBAC calling convention, Money / PhoneNumber |
@yourorg/proto |
Generated gRPC stubs — IdentityService.VerifyIdentity, PaymentService.InitiatePayment (orchestrator.v1) |
| Shared ESLint + Prettier config | Same rules in every repo; CI fails on violation |
| Husky (optional locally) | Pre-commit lint/format/type-check — CI remains the real gate |
OOP patterns (pragmatic)
Section titled “OOP patterns (pragmatic)”Use class/strategy patterns where the architecture already needs them — not enterprise boilerplate by default:
| Pattern | Where |
|---|---|
| Strategy + Factory/Registry | Workflow StageHandlerStrategy, payment/identity provider adapters |
| Repository | Selectively — complex reused queries or hard-to-mock access; otherwise call Prisma from services |
| Manual DI | Constructor / factory wiring at startup — no DI container required |
| Command-style | Workflow actions (validate → execute → emit); not ordinary CRUD |
Node EventEmitter vs RabbitMQ
Section titled “Node EventEmitter vs RabbitMQ”| Mechanism | Scope | Use for |
|---|---|---|
| EventEmitter | Same process, same service | Decouple in-process side effects after a domain change (audit row, cache invalidate, decide whether to write an outbox row) |
| RabbitMQ (via outbox) | Across services | Anything another service must see, and anything that must survive a crash |
Rules of thumb:
- Most notifications to users are eventually handled by Orchestrator — CAP/LMS emit domain events (outbox) that OL’s notification consumers handle; do not call SMTP from CAP.
- EventEmitter listeners are lost on crash if they have not finished — never use them alone for “must publish” cross-service work.
- Controllers should not call three unrelated services inline; prefer service-layer emit → local listeners / outbox write.
Quality checks
Section titled “Quality checks”- ESLint + Prettier (shared config)
- TypeScript
tsc --noEmit - Unit tests
- OpenAPI / route alignment check where wired
- Integration tests against real Postgres + RabbitMQ in CI (compose), not only mocks
See also
Section titled “See also”- Outbox pattern → System architecture
- CAP workflow strategies → Workflow abstraction