ADR: OpenAPI Generation vs Middleware Enforcement Boundaries¶
Status¶
Proposed
Date¶
2026-04-14
Decision Owners¶
Syfon maintainers
Context¶
There is active discussion about combining:
- OpenAPI-driven generated code (
apigenand potential generator changes such asoapi-codegen), and - Runtime middleware concerns (authn/authz, request/response validation, standardized error behavior).
These are related but different layers. Blurring them causes implementation confusion, especially around who is responsible for returning 400 vs 401/403 vs 500.
Decision¶
Adopt a layered contract:
- Generation layer (OpenAPI -> code) defines API shapes and typed plumbing.
- Middleware layer enforces cross-cutting runtime policy before/after handlers.
- Service/domain layer enforces business rules and backend-specific invariants.
Generator choice (openapi-generator vs oapi-codegen) does not replace middleware or service responsibilities. It changes how strongly typed and ergonomic the generated API boundary is.
Layer Responsibilities¶
1) Generation layer (apigen, generated routers/types/interfaces)¶
In scope: - Typed request/response models from OpenAPI. - Route scaffolding and handler interfaces. - Basic schema-driven validation hooks where supported by the generator/runtime.
Out of scope: - Authorization policy decisions. - Environment-specific RBAC behavior. - Business state validation (DB/object/workflow-dependent). - Operational logging/metrics policy.
2) Middleware layer (request pipeline)¶
In scope: - Authentication and authorization checks. - Request precondition checks (headers/content type/body size/basic format constraints). - Optional response contract validation. - Consistent error envelope/telemetry for cross-cutting failures.
Out of scope: - Object- and workflow-specific domain logic that requires service/database context.
3) Service/domain layer (service/*, domain handlers)¶
In scope: - Business invariants and workflow transitions. - Backend capability checks (storage/database/provider conditions). - Domain errors that should map to 4xx/5xx after domain evaluation.
Out of scope: - Generic authn/authz entry checks that should be centralized.
Error Ownership Matrix¶
| Status Class | Primary Owner | Typical Causes |
|---|---|---|
400 / 415 / 422 |
Generation + Middleware | malformed payload, unsupported content type, schema/request precondition failure |
401 / 403 |
Middleware | missing/invalid identity, insufficient permissions |
404 (routing) |
Router/Generation layer | unknown path/method |
404 (domain) |
Service layer | object/resource not found after valid route/auth |
409 / 412 |
Service layer | domain conflicts/precondition failures |
500 |
Middleware or Service | unexpected runtime or backend failure |
Notes:
- A generator may emit some 400 behavior, but production consistency still requires middleware + shared error mapping.
- Response validation in enforce mode may intentionally convert contract violations to 500.
RBAC Customization Model¶
RBAC is expected to vary per deployment. Therefore:
- Middleware should define extension points (policy resolver/authorizer interfaces).
- Route-to-policy mapping belongs in server configuration or policy registry, not generated OpenAPI models.
- OpenAPI may describe security schemes and required auth presence, but authorization semantics remain runtime-configurable.
This preserves portability: generated API contracts stay stable while RBAC strategy changes by environment.
Request Lifecycle (Target)¶
- Route resolution and typed decode (generation/router layer).
- Middleware authn/authz checks (
401/403). - Middleware request validation/preconditions (
400/415/422). - Handler/service business logic (
2xx/4xx/5xxdomain outcomes). - Optional middleware response validation (audit/enforce policy).
- Standardized error/log/metric emission.
Consequences¶
Positive¶
- Clear ownership boundaries reduce duplicated checks and regressions.
- Teams can migrate generator tooling independently from authz policy.
- Consistent client-facing error behavior despite backend or RBAC differences.
Tradeoffs¶
- Requires discipline to avoid reintroducing handler-level duplicate validations.
- Needs policy registry maintenance as routes evolve.
- Some validation may exist in both generated plumbing and middleware; overlap must be intentional and documented.
Rollout Plan¶
- Document current route/error ownership and identify duplicated checks.
- Introduce/standardize middleware interfaces for authz and request validation.
- Keep generated types/scaffolding as contract source of truth.
- Incrementally remove duplicate handler prechecks once middleware parity tests pass.
- If evaluating
oapi-codegen, run as a tooling migration track, not a policy migration.
Non-Goals¶
- This ADR does not select a final generator today.
- This ADR does not define one universal RBAC model.
- This ADR does not remove service-layer business validation.
Summary¶
Generated OpenAPI code and middleware are complementary:
- Generation gives typed API contracts.
- Middleware enforces cross-cutting runtime guarantees.
- Service layer owns business correctness.
They should be composed, not conflated.