# 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/<Area>/`. 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.
