# ADR 0005 — No separate Application layer - Status: accepted - Date: 2026-07-28 ## Context A conventional layering for a .NET service of this size would be Domain → Application → Infrastructure → Api, with a mediator and one handler per use case. DodoSSH's server is, in substance, CRUD plus one interesting write path ([ADR 0003](0003-sync-protocol.md)) plus a byte relay ([ADR 0004](0004-relay-authorization.md)). It performs **no cryptography on secrets and no business logic over item contents**, because it cannot read them. The genuinely hard logic — merge, key wrapping, trust decisions — lives on the client. ## Decision Three projects: `DodoSSH.Domain` (entities, enums, invariants, no EF), `DodoSSH.Infrastructure` (DbContext, configurations, migrations, queries), `DodoSSH.Api` (host, with vertical slices under `Features/`). Plus `DodoSSH.Contracts` for shared DTOs and `DodoSSH.Crypto` for the envelope format. No `Application` project, no mediator. ## Consequences - A feature is one folder under `Features/`: endpoint, request/response mapping, and the query or command inline. Reading an endpoint means reading one file, not tracing a request through a handler, a validator, a behaviour pipeline and a repository interface. - The testability argument for an Application layer does not apply here. The tests that matter for this server are authorization tests and sync-protocol tests, and both must run against a real PostgreSQL to be meaningful — an in-memory handler test would pass while the EF query filter, the advisory lock and the CHECK constraints all went unexercised. Integration tests with Testcontainers are the primary suite, so an extra seam buys nothing. - Business invariants that are genuinely invariant (permission algebra, relay target validation, cursor encoding) live in `Domain` as pure functions and are unit-tested there. That is where the fast tests belong. - **Accepted risk:** if the server later grows real domain logic — server-side policy evaluation, workflow, notifications with side effects — endpoints will start to get long. The mitigation is a rule, not a layer: when an endpoint file exceeds ~80 lines, extract a named service into `Features//`. Revisit this ADR if that happens three times in one area. - `DodoSSH.Crypto` is referenced by the API but only for format and fingerprint constants. It must stay free of any code path that could decrypt a payload server-side, so that the dependency cannot quietly become a capability. ### Rejected - **Application layer with MediatR.** Adds a hop and a handler per feature. Its usual payoffs — transaction/validation/logging behaviours, and decoupling from the web framework — are either already provided (endpoint filters, ProblemDetails) or irrelevant (there is no second host). - **Repository interfaces over EF Core.** `DbContext` is already a unit of work and EF's global query filters *are* the authorization backstop ([ADR 0002](0002-minimal-apis.md)). Wrapping it would hide the mechanism that keeps the API failing closed.