ADR-0008: OpenAPI tags scope the generated API interfaces

Every ODD Platform OpenAPI operation carries exactly one tag, and the generator emits one Java interface per tag — so a tag is the unit that shapes the generated *Api interfaces.

Status

Accepted. Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.

Context

ADR-0001 establishes that controllers implement OpenAPI-generated *Api interfaces rather than carrying their own HTTP mappings. That raises a follow-on question the spec must answer: what determines how those generated interfaces are carved up? With the spring generator, the answer is the OpenAPI tag — but only if the spec uses tags consistently. If operations carried several tags, or none, the generated interface boundaries would be ambiguous or arbitrary.

ODD's openapi.yaml adopts a deliberate tagging convention so the generated interfaces are predictable.

Decision

Every operation carries exactly one tag, and the generator is configured to emit one interface per tag (useTags: true). A tag is therefore the unit that shapes the generated API surface: tag alert produces AlertApi, tag search produces SearchApi, tag dataEntity produces DataEntityApi, and so on. Across all 194 operations in the spec there are no multi-tagged and no untagged operations, so every operation lands in exactly one generated interface — there is no ambiguity about which interface an endpoint belongs to.

Tags are resource-oriented, and for most resources the tag also lines up with a single URL prefix. Of the 33 tags, 30 group operations that all sit under one /api/<resource> path prefix (for example every alert operation is under /api/alerts, every search operation under /api/search). For those resources the tag, the URL prefix, and the generated interface coincide cleanly.

Three tags deliberately group a resource whose operations span more than one path root, so the tag is broader than a single URL prefix:

  • dataEntity (the largest, ~37 operations) spans /api/dataentities/** and /api/dataentitygroups/** — the entity and its group sub-resource.

  • ownerAssociationRequest spans /api/owner_association_request/** and the owner-mapping operations under /api/owners/**.

  • dataCollaboration spans /api/datacollaboration/** and the message-resolution operation under /api/messages/**.

For these, the tag is the resource boundary and the generated interface aggregates operations across the related paths. The invariant the spec actually holds is one tag per operation → one interface per tag; the "one tag = one URL prefix" alignment is the common case (30 of 33), not a universal rule.

Consequences

  • A contributor editing openapi.yaml controls which generated interface an endpoint belongs to by setting its tag; the controller then implements that interface (per ADR-0001). Tag choice is an interface-design decision, not cosmetic.

  • The generated *Api interfaces are resource-shaped and stable, which keeps controllers small and their grouping predictable.

  • For the three multi-root tags, an endpoint's URL prefix does not by itself tell you its interface — the tag does. A reader mapping a path to its generated interface must read the operation's tag, not infer it from the URL.

  • Because grouping is driven entirely by the tag, a missing or wrong tag on a new operation would misplace it in the generated surface; the single-tag-per-operation discipline is what keeps the mapping unambiguous.

Evidence

  • odd-platform-api-contract/build.gradle:10,24generatorName = "spring" with configOptions including useTags: "true", which makes the generator emit one interface per tag; :16 sets apiPackage = "org.opendatadiscovery.oddplatform.api.contract.api" and :44 wires compileJava.dependsOn tasks.openApiGenerate.

  • odd-platform-specification/openapi.yaml — across all 194 operations, each declares exactly one tags: entry (no operation is multi-tagged or untagged); 33 distinct tags are used.

  • Clean single-prefix examples: every alert operation is under /api/alerts (e.g. getAllAlerts on GET /api/alerts); every search operation under /api/search (e.g. search on POST /api/search).

  • Multi-root tags: dataEntity operations appear under both /api/dataentities/** and /api/dataentitygroups/**; ownerAssociationRequest under /api/owner_association_request/** and /api/owners/**; dataCollaboration under /api/datacollaboration/** and /api/messages/**.

See also

Last updated