Service Registration
ServiceRegistrar registers a microservice via NATS™ lifecycle events and
maintains a heartbeat. The BE subscribes to these events and persists the state
to the service_registry table. The microservice never touches the database
directly.
Lifecycle events
Three NATS™ subjects carry lifecycle events:
| Subject | When | Payload |
|---|---|---|
service.register | On startup + on gateway restart | ServiceRegisterPayload — code, base_url, endpoints, health, metadata, capabilities |
service.heartbeat | Every 30s (default) | ServiceHeartbeatPayload — code, base_url, health, metadata |
service.unregister | On graceful shutdown | ServiceUnregisterPayload — code, base_url |
service.stale | Watchdog-detected silence | ServiceStalePayload |
service.gateway_online | BE startup (broadcast) | { at } — every live service re-registers |
Gateway restart re-registration
service.register is a request/reply handshake: the registrar retries every
5 s until the BE acks { registered: true } — a missing/negative ack means
no heartbeats. After the first successful registration the service subscribes
to service.gateway_online: when the BE restarts it announces itself and
every live service re-registers, so the gateway re-ingests
identity/endpoints/capabilities and re-discovers OpenAPI without polling.
service.gateway_online is a broadcast — deliberately NOT a queue group —
because every running service instance must observe it.
These subjects are defined in the SERVICE_SUBJECTS constant.
Usage
Code
Nav declaration — validated at enrollment
A module declares its frontend surface in endpoints.nav (a
ModuleNavDeclaration): route_prefixes, nav links, and the routes
census. At service.register the BE runs validateNavDeclaration(code, decl, partOf) and rejects the enrollment on any violation — the
declaration never reaches service_registry.
The enforced conventions:
| Rule | Contract |
|---|---|
| Primary prefix | route_prefixes[0] must be /{part_of}/{code}; extra prefixes keep code as the URL's second segment |
| R1 — list plural | kind:"list" routes end in a plural leaf (pluralize) |
| R2 — nav leaf | a leaf nav href resolves to a declared route, or is itself plural |
| R3 — derived leaves | kind:"create" ends in /create; kind:"detail" ends in [param] |
access | optional access.{app}.{module}.{page} gate — must prefix-match the module's own grant |
Route paths are derived, never literals — entityPaths(prefix, listLeaf, detailParam?) is the single constructor of the entity-route
shape:
Code
Unit-test the declaration at write time — the same function the BE calls at enrollment:
Code
Health checks
The healthCheckFn (third constructor argument) is called on each heartbeat to
include the current health status. It returns http_healthy (boolean) and
checks (a record of named checks with ok and optional error).
If no healthCheckFn is provided, the registrar defaults to http_healthy: true
with empty checks.
Configuration options
ServiceRegistrarConfig supports:
| Field | Required | Description |
|---|---|---|
serviceCode | yes | Unique service identifier |
baseUrl | yes | Service URL (used by BE for direct mode routing) |
endpoints | yes | Map of endpoint signatures |
heartbeatIntervalMs | no | Heartbeat interval (default: 30000) |
name | no | Display name |
description | no | Service description |
author | no | Author name |
github_repo_url | no | Repository URL |
service_version | no | Semver string |
is_behind_scaler | no | Whether the service is behind a scaler (default: false) |
icon / icon_type | no | Icon for UI display |
ServiceRegistryPort
The BE implements ServiceRegistryPort to persist lifecycle events. The port
interface supports both scaler mode (one row per code) and direct mode (multiple
rows per code, distinguished by base_url):
| Method | Mode | Description |
|---|---|---|
findByCode(code) | both | Find one row by code |
findByCodeAndBaseUrl(code, baseUrl) | direct | Find by code + URL |
findAllByCode(code) | direct | All rows for a code |
findAll() | both | All rows |
insert(row) | both | Insert new row |
updateByCode(code, row) | scaler | Update by code |
updateByCodeAndBaseUrl(code, baseUrl, row) | direct | Update by code + URL |
deleteByCodeAndBaseUrl(code, baseUrl) | direct | Delete by code + URL |
Next steps
- NATS Client — the transport used by ServiceRegistrar
- Authentication — auth for NATS™ subscribers
- API Reference — ServiceRegistrar and payload types