Skip to content

Ship the registry with the fleet it lists

Module 7’s registry was a good idea with an awkward owner. A table names the fleet, and the deploy reads it. But somebody had to create that registry database, create the table, and put the rows in, and none of that was in your package. So onboarding a tenant meant an INSERT someone ran by hand, against a table nobody reviewed, and the deploy believed whatever it found.

Put the control plane in the product. One template builds the registry and fills it from a file. A second template reads it and deploys the fleet. Onboarding becomes a line in a pull request.

The lab’s Product.json names two templates, and the order matters:

"TemplateOrder": [ "Registry", "Tenant" ],
"ScriptTokens": { "ControlDb": "fleet_control", "ReleaseVersion": "1.0.0" },

Registry deploys to fleet_control. The settings file names it in TemplateTargets with CreateIfMissing: true (Module 3’s flag), so the first run creates it. Its one table declares its own rows:

"DataDelivery": {
"ContentFile": "data/dbo.Tenants.tabledata",
"MergeType": "Insert/Update/Delete",
"MatchColumns": "DatabaseName"
}

Insert/Update/Delete makes the content file the whole roster. A row in the file is a tenant in the fleet, and a row taken out is a tenant gone from it.

Tenant finds its databases the way Module 7 did, by querying the registry, with IdentificationDatabase: "{{ControlDb}}" pointing the query at fleet_control. It declares the Shop tables, a ProductVersion table, and a VersionStampScript that writes {{ReleaseVersion}} into each tenant at the end of its deploy.

Terminal window
schemaquench --ConfigFile:quench.settings.json
Quenching Template: Registry
Creating database [fleet_control] (CreateIfMissing: true)
...
[localhost,11433].[fleet_control] Delivering dbo.Tenants
[localhost,11433].[fleet_control] Successfully Quenched
Quenching Template: Tenant
Template 'Tenant': 3 units - db: DatabaseIdentificationScript; schema: (regular template)

fleet_control didn’t exist when the run started. By the time Tenant asked who’s in the fleet, the registry had been created and filled, so the answer was the three tenants in the file. Each one’s ProductVersion reads 1.0.0.

Add a fourth row to the content file and deploy again. Tenant reports 4 units, and only fleet_tenant_004 gets new tables. The tenant joined through a reviewed change to the package, and the same run delivered its row and deployed it.

Bump ReleaseVersion to 1.1.0 and deploy, and every tenant’s stamp moves to 1.1.0. The stamp is written at the end of each tenant’s deploy, so a tenant that failed partway still says 1.0.0. Reading the stamps across the fleet is how you find who didn’t get the release.

Now reverse it, "TemplateOrder": [ "Tenant", "Registry" ], and add a fifth tenant in the same change. Deploy:

Quenching Template: Tenant
Template 'Tenant': 4 units - db: DatabaseIdentificationScript; schema: (regular template)
...
Quenching Template: Registry

Four units, not five. Tenant read the registry before Registry delivered the new row. Run it again and it’s 5 units: the tenant arrives one deploy late.

Nothing failed. Nothing warned. That’s the part to remember. TemplateOrder isn’t a display order, it’s a dependency order. A template that reads what another template writes has to come after it, and when it doesn’t, the only symptom is a fleet that’s always one release behind its own roster.

Check yourself: A teammate puts the Tenant template first so the fleet deploys before the registry. Onboarding a tenant still works, they say: it shows up eventually. What's the actual cost?

Every onboarding takes two deploys instead of one. The Tenant template reads the registry before the Registry template delivers the new row, so the new tenant isn’t in the roster that run and is first deployed on the next one. Nothing fails or warns, so the delay is easy to miss. In the order Registry, then Tenant, the run that adds the row also deploys the tenant.


A shop that forges its own tongs before it picks up the work doesn’t need to borrow a pair from the shop next door. That’s this module: the product brings the registry it reads from, builds it first, and fills it from a file you review. Order the templates the way the work depends on itself. Registry, then the fleet. Write the roster, then read it.

Running a product where one template has to wait on another and you’re not sure the order is holding? Email me at forgebarrett@schemasmith.com — I read every one.

Until then, may every tenant you list be the one the next run reaches.

— Forge