Skip to content

Run the registry

This module does not own a server. go/transport already provides one with TLS, body limits, security headers, health endpoints and go/controls lifecycle, and a registry that reimplemented those would be a second answer to a question the estate has answered once.

Register it with the controller

srv, err := serve.Register(ctx, "schema-registry", controller, logger,
    registry,                    // the read half
    registry,                    // the write half, or nil for read-only
    transporthttp.ServerSettings{Host: "0.0.0.0", Port: 8080},
    serve.Authenticate(requireToken),
)

Register does not start anything — go/controls does, when it starts the service. A misconfiguration therefore fails at wiring, where an operator is watching, rather than at the first request.

The write path needs authentication

Mounting a publisher with nothing in front of it is refused

serve.Mount(registry, registry)  // ErrNoAuthentication

Publishing is privileged and reading is not, and the two handlers are only separate so that difference survives wiring. A mount that produced an open write path by omission would undo that in the one place nobody looks.

If it is genuinely wanted — a test, or a network whose boundary is somewhere else entirely — serve.AllowAnonymousPublishing() says so out loud and leaves a grep-able string in the source.

The read handler is mounted at the root and the write handler under /publish, so a reverse proxy or a policy engine has one prefix to protect rather than a method to discriminate on.

Your middleware sees the path as it arrived

PUT /publish/schemas/orders.created/3, prefix intact. The prefix is stripped inside the middleware, not outside it.

That ordering is load bearing. go/transport's AuthMiddleware keys on r.URL.Path, and WithAuthSkipper predicates on the request — so if the prefix were stripped first, the natural policy "reads are public, skip auth under /schemas" would skip authentication for every publish, and the audit log would record each one as a read.

Read-only is the common deployment

Schemas published by CI, read by everything:

srv, err := serve.Register(ctx, "schema-registry", controller, logger,
    registry, nil,
    transporthttp.ServerSettings{Host: "0.0.0.0", Port: 8080},
)

No authentication is needed and none is demanded. /publish answers nothing.

Point a service at it

resolver, err := client.New("https://schema.internal.example")
if err != nil {
    return err
}

guard := schema.NewGuard(resolver, []schema.Validator{jsonschema.New()})

The client bounds itself to two seconds by default. A consumer is holding a message and deciding whether to validate it; waiting thirty seconds to learn the registry is down is worse than failing open in two, which is what the guard does anyway.

Storage

MemoryStore is what ships. It is a perfectly good backing store for a single instance whose schemas are published by CI on every deploy, and it is not durable.

Anything else implements the four-method Store interface. One requirement is not negotiable: prior versions must be retained. A store that kept only the latest would make the compatibility retrofit require a backfill nobody has the data for.