Why a companion module¶
transport-openapi could have been a package inside
go/transport — it mounts on a transport
server's mux and depends on transport's security-header middleware. It is a
separate module for one concrete reason: the size of what it embeds.
The 2.4 MB embed¶
The interactive docs UI is Stoplight Elements, vendored as two files:
| Asset | Size |
|---|---|
web-components.min.js |
~2.08 MB |
styles.min.css |
~297 KB |
Both are //go:embed-ed into the binary. That is the price of shipping a
self-contained docs site — a project needs only its generated spec, never a
per-project copy of the UI front-end. It is a price worth paying when you serve
docs.
If this lived in go/transport core, that ~2.4 MB would be linked into every
transport-server binary — including the many services that expose no public API
docs at all. A background worker with a health endpoint would carry a 2.4 MB
Stoplight blob for no reason.
Opt-in by module boundary¶
Making it a separate import moves the cost to exactly the tools that want it:
- Import
go/transport→ you get the server stack, no docs UI, no embed. - Also import
go/transport-openapi→ you opt into the docs handler and its embed.
Go's linker only includes packages that are imported, so the boundary is the
switch. This mirrors the transport-metrics companion and the general
transport-<companion> pattern in the toolkit: the transport core stays lean,
and heavier or more specialised concerns live in companions that a tool adds only
when it needs them.
What it still shares¶
Being a separate module does not mean duplicating transport. transport-openapi
depends on go/transport and go/transit — it reuses their security-header
middleware and Middleware type rather than reimplementing them. It is a
companion, not a fork: lean core, opt-in extras, shared contracts.