Service Layer Pattern
Routes are thin HTTP handlers; use-case logic lives in services. Since I#2013 the target topology is three layers — see Persistence Topology:
Architecture
Route (HTTP adapter) -> Service (actor, authz, validation, DTO) -> Domain module (operations + Drizzle)
Routes handle: request validation, HTTP status codes, response formatting.
Services handle: ServiceActor, authorization, Zod validation, i18n error mapping, DTO shaping, use-case coordination.
Domain modules (domain/<x>/, interim lib/<x>/) own all persistence. New service code must not import @/db — legacy violators are allowlisted and migrate on touch.
Service Directory
All services live in backend/src/services/ (148 files as of 2026-06). Each service has a matching interface in backend/src/interfaces/ (71 files including infrastructure interfaces).
| Service | Responsibility |
|---|---|
buildings-service.ts | Building CRUD, geometry, energy data |
quote-service.ts | Quote lifecycle, line items, pricing |
hubspot/ | CRM sync — see HubSpot Integration |
portal/ | External portal logic — see Portal |
document-template/ | PDF template management — see PDF Templates |
feedback/ | User feedback collection — see Feedback System |
create-services.ts | DI container assembly — see Dependency Injection |
Route Service Access (MANDATORY)
Routes access services via Hono context. Always destructure directly:
const { documentRequests, portal } = c.var.services;
// NEVER alias:
// const requestService = c.var.services.documentRequests;For infrastructure/cross-cutting concerns, use c.var.container:
const { infrastructure, crossCutting } = c.var.container;Service Reuse
Services are injected via Dependency Injection, making them reusable across:
- Routes — HTTP request handlers
- Other services — cross-service orchestration
Not across job processors: since I#2013, processors are batch presentation and consume the domain layer directly as peer clients of services (ProcessorContainer has no services slice) — see Persistence Topology.
Interface Contract
Every service implements a corresponding interface from backend/src/interfaces/:
// backend/src/interfaces/buildings-service.ts
export interface IBuildingsService {
getBuilding(id: string): Promise<Building>;
createBuilding(data: CreateBuildingInput): Promise<Building>;
}This enables testability and loose coupling.
See Also
- Persistence Topology — the I#2013 layer map and ratcheted gate
- Backend Architecture — overall backend structure
- Dependency Injection — how services are wired
- Routes Overview — route organization
- Services Overview — complete service catalog
- Middleware Stack — request pipeline before services