0006 — No runtime schema registry
Event schemas are generated artifacts checked in CI, not a running service.
Decided. Event payload schemas are generated from the entity spec, committed to the repo, and enforced in CI. There is no registry service, no KV buckets, no startup handshake.
Alternatives
- A first-party registry service backed by KV (legacy: a NATS service with
schemasandmanifestsbuckets, a startup manifest handshake, and a compatibility endpoint). - An external registry — Confluent, Apicurio, Buf.
Why
Separate two things the legacy design conflated: schemas (checked artifacts) and a registry (a running service with storage and an API). Nearly all the value is in the artifacts, and the primary compatibility check costs nothing.
The events/ package is outside internal/ because it is a published contract.
A consumer imports it directly:
import accountingevents "dev.azure.com/lifeinflow/Flow/flow-technology/services/accounting/events"
var ev accountingevents.AccountCreated
json.Unmarshal(env.Data, &ev)The compiler is the compatibility check. The producer renames a field, changes a type, or removes something the consumer references — the consumer stops building. No tooling, no schema diff, no CI step needed for this. Producers and consumers live in one module, so the import is always resolvable and always current.
Worth noting: the legacy registry's compatibility check is a stub that accepts every change. Its own comment says so. So dropping the service loses nothing currently enforced; we are building the gate for the first time either way.
What CI enforces
- Regenerated schemas match what is committed.
- Diffs within an
event_typeare additive only — no field removed, renamed, retyped, or newly required. Breaking changes become a newevent_type. - No
pii:"true"field appears in an event type declaredcontains_pii = false. - Golden fixtures: marshal a fixed value, compare bytes. This is what catches a renamed JSON tag before the rolling deploy window does.
Consequences
- Consumers must decode leniently. Never
DisallowUnknownFieldson event decoding — that turns every producer's additive change into a consumer outage. - The schema is derived from the Go struct, so it cannot drift from the code.
- Cross-repo consumers would lose the build-time check. An argument for keeping producers and consumers in one repo.