ADR-0001: Contract-first HTTP layer
ODD Platform's REST controllers are thin delegates over OpenAPI-generated interfaces — the HTTP contract lives in the spec, and routes change by regenerating it, not by editing controllers.
Status
Accepted. Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.
Context
ODD Platform exposes a large REST surface (hundreds of endpoints across data entities, ingestion, search, lineage, RBAC, collaboration, and more). A platform that size needs one answer to a structural question: where does the HTTP contract live, and how is it kept consistent between the server that implements it and the clients (the web UI, collectors, push adapters) that call it?
There are two broad options: hand-write Spring MVC/WebFlux mappings on each controller, or define the contract once in a specification and generate the wiring from it. Hand-written mappings drift — the spec and the code diverge silently, and each client re-derives the contract by hand. ODD chose the contract-first path.
Decision
Every REST controller in odd-platform-api is a thin delegate that implements an OpenAPI-generated *Api interface. The HTTP method, path, and produces/consumes annotations live on the generated interface — compiled from openapi.yaml into the org.opendatadiscovery.oddplatform.api.contract.api.* package by the odd-platform-api-contract module's openApiGenerate build step — never on the controller class itself.
A controller class carries only @RestController and @RequiredArgsConstructor; each method carries only @Override and delegates straight to a service. Consequently, adding or changing an endpoint is a specification-edit-and-regenerate flow, not a controller edit.
Consequences
openapi.yaml(inodd-platform-specification) is the single source of truth for the HTTP surface. The UI's generated TypeScript client and the server's generated interfaces stay in sync by construction.To add an endpoint: edit
openapi.yaml, regenerate, then implement the@Override. Putting a@PostMapping/@GetMappingdirectly on a controller class does not follow the convention — it would create a route that lives outside the contract and silently drift the spec from the code.The generated
*Apiinterfaces are build artifacts and are not committed to the source tree; to read the live contract, readopenapi.yaml.Two deliberate exceptions, both external-webhook receivers.
AlertManagerControlleris fully hand-coded — it bridges an external Alertmanager webhook payload that is not part of ODD's generated contract.EventApiControlleris also fully hand-coded — it receives the Slack events webhook via a direct@PostMapping("/api/slack/events"), again with no generated interface. Both are a single@RestControllerwith a direct@PostMappingbecause each bridges an external system's webhook payload that is not part of ODD's contract — the deliberate exceptions that prove the rule.
Evidence
odd-platform-api/.../controller/AlertController.java:11-17—@RestController @RequiredArgsConstructor public class AlertController implements AlertApi; the class declares no@RequestMapping, and its methods are@Overridewith no mapping annotations.The interface is imported from
org.opendatadiscovery.oddplatform.api.contract.api.AlertApi— generated by theodd-platform-api-contractmodule'sopenApiGeneratetask at build time and absent from the source tree.odd-platform-specification/openapi.yaml— the contract the*Apiinterfaces are generated from.The same shape holds across the module's controllers (data-entity, ingestion, search, and RBAC controllers all
implementstheir generated*Apiwith no controller-level mapping annotations). The two deliberate exceptions are the external-webhook receivers, both fully hand-coded with no generated*Api:AlertManagerController.java:15-21(@RestController, noimplements,@PostMapping("ingestion/alert/alertmanager")with a// TODO: define OpenAPI specnote) andEventApiController.java:14-22(@RestController, noimplements,@PostMapping("/api/slack/events")— the Slack events webhook).
See also
How to contribute — the contribution workflow, including the spec-and-regenerate loop.
Build and run ODD Platform — building the module that runs the OpenAPI generation.
Last updated