Skip to content

Why a URN and not a URL

The obvious thing to put in dataschema is a URL:

https://schemas.phpboyscout.uk/orders/created/v3.json

It is dereferenceable, everybody understands it, and it needs no registry to be useful on day one. We did not do that.

The argument

A dataschema outlives the infrastructure that served it.

Once a message carries one, that string is in log lines, in archived object storage, in a JetStream stream and in whatever a consumer wrote down. Three years later the schema host has been renamed, moved behind a different gateway, or replaced with something else entirely — and every one of those historical messages now points at a dead host.

Nothing rewrites them. They are not editable; that is what makes them a record.

A URN separates identity from location. urn:phpboyscout:schema:orders.created:3 names a schema and says nothing about where it lives, which is what the specification's own wording asks for: "Identifies the schema". Resolution then becomes configuration rather than a promise embedded in every message — embedded in the binary, cached, or fetched over the network, and changeable without touching a single archived event.

The cost, stated

It is not dereferenceable. Somebody outside the estate holding one of our messages cannot fetch the schema without knowing our resolver, where a URL would have just worked.

That is acceptable for internal traffic. It would not be if we published events externally, and if that changes, the decision to revisit is this one.

The hybrid we did not rule out

Publishing at a URL as well, with the resolver mapping URN to URL, is additive: the URN does not change when a URL appears beside it, and no archived message is invalidated.

The one thing that would be awkward to retrofit is that the URLs would ideally exist from the first published message rather than appearing halfway through the archive. That is a reason to decide sooner rather than a reason to decide now.

Why the version is an integer

orders.created:3, not :v3.1.0 and not :2026-08-28.

Semantic versioning would imply a compatibility promise, and go/schema does not check compatibility in v1. A promise nobody verifies is precisely the class of claim this estate has spent the past week removing. A date encodes when rather than what, and answers no question a consumer asks.

An integer is unambiguous, orders trivially, and claims nothing.