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/providers/listis forwarded to the EmailSender microservice atGET /api/v1/entities/providers/list. - 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 required env vars at startup - Config loading —
ConfigLoaderreads theconfigtable 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 - 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/patches/directory, applied via@primebrick/sdk'sapplyPatches()runner - Schema override: the
@Entitydecorator accepts an optional second argument for the schema name (e.g.@Entity("service_registry", "public"))
Next steps
- Conventions — API path conventions, data model rules
- EmailSender — Email sending microservice