Architecture
Primebrick v3 microservices follow a gateway-proxied architecture with NATS™ for async messaging and per-service database isolation.
Backend proxy
The Backend (BE) acts as the API gateway for all microservices. The Frontend (FE) never calls microservices directly.
-
HTTP proxy: The BE proxies requests via
/ws/:serviceCode/*. A request toGET /ws/emailsender/api/v1/entities/provider/listis forwarded to the EmailSender microservice atGET /api/v1/entities/provider/list.Code -
OpenAPI aggregation: The BE fetches each microservice's
GET /api/v1/openapi.jsonand merges the specs into a unified API catalog. This powers the Zudoku API explorer on the docs site. -
MCP Server: The BE's MCP Server uses the aggregated OpenAPI specs to generate generic CRUD tools. The standardized entity CRUD path pattern (
/api/v1/entities/:entity/...) allows the MCP Server to dispatch tools without per-entity path configuration.
NATS™ message bus
Microservices connect to NATS™ via the SDK's NatsClient. NATS™ is used
for:
- Service registration — microservices publish register/heartbeat/
unregister events; the BE subscribes and persists to
public.service_registry - Async request/reply — the BE publishes requests (e.g.
emailsender.send) and microservices reply on a per-request response subject (e.g.emailsender.response.{requestId})
NATS™ authentication uses the SDK's GATEWAY-RESOLVED mode — the BE forwards
JWT/auth headers via NATS™ message headers, and the microservice verifies
them with verifyNatsMessage().
SDK lifecycle
Every microservice uses @primebrick/sdk for its lifecycle:
- Environment validation —
requireEnv()validates the bootstrap env vars at startup (DATABASE_URL,DATABASE_SCHEMA— the only env-sourced settings, validated closed-set byassertDbEnv(); all runtime config lives in the service'sconfig_entriestable) - Config loading —
ConfigLoaderreads theconfig_entriestable for service-specific settings (NATS™ URL, HTTP port, service code) - Auth config —
initAuthConfig()+loadAuthConfig()set up GATEWAY-RESOLVED auth mode - NATS™ connection —
NatsClient.getConnection(url) - Service registration —
ServiceRegistrar.register()publishes the register event, thenstartHeartbeat()begins periodic health checks - HTTP server —
createHttpServer()starts the HTTP listener with health check and route handler. TherouteHandlerMUST be factory-produced (makeRpcRouter/makeOpenApiHandler/composeRouteHandlers) —createMicroservicerejects arbitrary handlers at startup, so the auth → RBAC → validation → RFC 7807 chain cannot be bypassed - Graceful shutdown —
GracefulShutdowncoordinator runs cleanup on SIGTERM/SIGINT: stop heartbeat → unregister → close NATS™ → close DB → close HTTP
Database isolation
Each microservice has its own PostgreSQL® schema (e.g. emailsender).
Microservices never read or write to another microservice's schema. The
only shared table is public.service_registry, which lives in the
public schema and is used for service registration.
- DAL:
@primebrick/dal-pgwith entity decorators (@Entity,@Column,@Key,@Unique,@AuditableField,@DeletableField) - Migrations: each microservice has its own
db-meta/directory (patches/+ signedMANIFEST.json), applied by the dal-pgrunMigrationsengine at service boot (dbPatchesoption) - Schema override: the
@Entitydecorator accepts an optional second argument for the schema name (e.g.@Entity("service_registry", "public"))
Single DAL API: services use getDal() only — the pg pool is internal
to the Dal object and must never be touched by consumers.
Audit vs events: every entity write produces (a) the persistent
{table}_audit row via dal-pg AuditPort and (b) an entity-changed event
signal for whoever subscribes — two separate mechanisms; the BE's
collaboration/SSE layer is just one consumer of the event.
Translations are per-module: each microservice owns
<schema>.translations (e.g. emailsender.translations) for its module
keys; app.* keys are global and live in public.translations (BE-owned),
system.* in system.translations. The BE's translations gateway manages
CRUD across module tables.
Next steps
- Conventions — API path conventions, data model rules
- EmailSender — Email sending microservice