* refactor(api): enforce service and data-service boundaries * refactor(api): complete feature service boundaries * refactor(identity): enforce service and data boundaries * refactor(vendors): enforce service and data boundaries * refactor(workorders): enforce service and data boundaries * refactor(backend): enforce architecture and optimize dispatch * style(backend): format changed architecture files * fix(architecture): address backend review follow-ups * fix(backend): sanitize exception disclosure in changed API endpoints Replace raw exception-message disclosure (ex.Message) returned to API callers with a stable sanitized public message plus correlated structured internal logging, across the endpoints changed in this PR. - Add SanitizedErrors helper: logs the original exception at Error with a generated correlation id and returns a stable public message referencing it so support can trace without exposing internals. - Inject ILogger<T> into the 14 changed controllers and route every ex.Message/dbex.Message disclosure through the helper, preserving status codes, response shapes, and business data (e.g. OpenWorkOrders). - Leave FluentValidation (vex.Errors) and existing fixed-message catches untouched; out-of-scope controllers (Account/Contact/Employee/Asset/ PMSchedule) are unchanged. - Add focused tests proving internal exception text is not returned and that Error logging carrying the original exception is invoked. * fix(architecture): abstract job run state access * style: format board update service * test: use collection assertion idiom
4.7 KiB
Backend Architecture and Quality Rules
This repository uses a feature-oriented service boundary over Entity Framework Core:
HTTP controller -> I{Feature}Service -> I{Feature}DataService -> ApplicationDbContext
The interfaces are architectural seams, not a requirement to wrap every class or every EF method. Add an interface when it separates HTTP, business, persistence, or external I/O responsibilities and enables behavior-focused testing.
Layer responsibilities
API controllers
- Parse HTTP input, authorize the caller, invoke a business-service interface, and map the result to the established HTTP contract.
- Do not inject
ApplicationDbContext, concrete service implementations, or concrete data-service implementations. - Do not contain persistence queries or business workflows.
- Do not expose exception messages to clients. Map known failures explicitly and let the centralized exception boundary handle unexpected failures.
Business services
- Own validation, business decisions, orchestration, and transaction intent.
- Depend on interfaces for persistence and external I/O.
- Use
IOptions<T>for typed configuration. Do not injectIConfiguration. - Keep feature responsibilities cohesive. Split a service when it has independently changing business reasons, not because it crossed an arbitrary line count.
- Preserve existing API and board-backed behavior unless the accepted requirement explicitly changes it.
Data services
- Own EF Core query shape, persistence, batching, and transaction implementation.
- Accept cancellation tokens on new I/O methods and pass them to EF Core.
- Use
AsNoTracking()for read-only queries. - Project only required columns for list/read models; include full graphs only when the caller needs them.
- Page unbounded collections at the database.
- Avoid query-in-loop and save-in-loop patterns. Prefer set-based reads and batched writes while preserving the workflow's failure and transaction semantics.
- Do not introduce a generic repository over EF Core. Feature data services should expose intent-revealing operations.
Performance review
For each changed hot path, document and test:
- input size variables;
- CPU and memory complexity;
- database round trips;
- whether filtering, ordering, and paging execute in SQL;
- whether tracking is necessary;
- whether repeated work can be hoisted out of loops;
- the partial-failure and transaction behavior.
Prefer dictionary or hash-set reconstruction when database results must follow caller
order. A query followed by dictionary reconstruction is normally O(n) CPU and memory;
repeated First/Single scans over the result are O(n²).
Do not optimize by weakening correctness. In particular, batching all writes into one final save can change partial-success behavior, and adding a transaction can change locking and retry semantics. Those are business decisions, not mechanical cleanup.
Configuration
Configuration is bound once during composition in
SeaHaven.Services/DependencyInjection/ServicesModule.cs. Business services receive
typed options such as FrontendOptions, JwtOptions, ApprovalsOptions, and
VendorPortalOptions.
Defaults must preserve current behavior. Startup validation should be introduced only when every deployed environment is known to provide the required value.
Enforcement
ArchitectureTests enforces mechanical dependency rules:
- controllers cannot inject EF contexts or concrete service/data-service classes;
- business services cannot inject EF contexts or raw
IConfiguration.
The pull-request quality workflow runs those tests and verifies formatting for changed C# files. The normal CI workflow builds the solution and runs the full test suite.
Architecture tests are appropriate for dependency direction. Behavior belongs in tests through public interfaces. Do not add tests merely to prove a linter or analyzer itself works; test the product behavior the rule protects.
Review checklist
- Controller depends only on service abstractions.
- Business logic is in a cohesive feature service.
- EF operations are behind a feature data-service abstraction.
- No raw
IConfigurationin a business service. - No unbounded load followed by in-memory filtering.
- No query/save loop where a set-based operation preserves semantics.
- Read-only EF queries use no tracking.
- New async I/O accepts and propagates cancellation where the public contract permits.
- Errors do not leak internal exception details.
- Tests cover behavior, authorization, data contracts, ordering, duplicates, missing records, and relevant failure boundaries.
- Changed behavior has been checked against the authoritative ticket board. Missing board access blocks readiness and merge claims.