Backend Technologies¶
The fleet is polyglot by fit
(ADR-008): Go where
proxying, resilience and event throughput dominate; TypeScript where auth tooling,
fast-iterating rules and query-shaped work dominate. Both sides share the same
hexagonal layout (domain / application / adapters / config), so the languages
differ but the architecture reads identically.
Go side¶
api-gateway, passport-service, fridge-service, measurement-pipeline.
Go¶
What: Statically typed, compiled language with goroutine-based concurrency.
Why: Go compiles to a single static binary, which gives ~15 MB Alpine images and instant container starts — exactly what a 7-service fleet on a laptop (and later Kubernetes) needs. Its explicit error handling and lack of framework magic suit the services where correctness under failure is the point: the gateway (proxying), the passport ACL (circuit breakers toward flaky external sources), the fridge fold (event-sourced state) and the measurement pipeline (Kafka throughput).
chi¶
What: A router on top of net/http — handlers are plain stdlib handlers.
Why: The gateway needs route groups (the endpoint-ownership table) and middleware
(request IDs, logging, panic recovery, later auth and rate limiting); chi provides
both without replacing the standard library's request model. Anything written for
net/http — including httputil.ReverseProxy, which powers the gateway — plugs in
unchanged.
pgx + sqlc¶
What: pgx is the canonical Postgres driver for Go; sqlc compiles hand-written
SQL files into type-safe Go functions at build time.
Why: Repositories stay explicit: the SQL that defines an aggregate's persistence is
visible in queries/, and the compiler catches a renamed column before runtime. No ORM
sits between the domain model and the schema — important for the append-only fridge
event table and the tenant-scoped queries that ADR-001 requires to be auditable.
Migrations run per service schema via golang-migrate.
franz-go¶
What: A pure-Go Kafka client.
Why: Part III of the course is explicitly about topic design, partitioning keys,
consumer groups and delivery semantics. franz-go keeps those concepts in our code
instead of hiding them behind a framework, performs well, and avoids CGo — keeping the
static-binary Docker story intact (unlike confluent-kafka-go, which wraps a C
library).
testify¶
What: Assertion helpers on top of the stdlib testing package.
Why: The stdlib runner stays in charge (no custom test framework); testify just removes assertion boilerplate.
TypeScript side¶
identity-service, personalization-service, brand-analytics-service.
TypeScript / Node.js¶
What: Typed JavaScript on the Node 24 LTS runtime.
Why: These three services are people-and-rules shaped: identity sits next to Keycloak and the consent ledger, personalization iterates on versioned screening policies, brand-analytics serves dashboard queries. TS iterates fastest there, and it is the same language as the SvelteKit SPA — verdict and profile types stay close to the code that renders them. Running both Go and TS also makes the microservice independence claim real rather than asserted.
Fastify¶
What: A fast, low-overhead Node web framework with first-class JSON Schema validation of requests and responses.
Why: Fastify validates routes with the same technology our event contracts use (JSON Schema), so one mental model covers both HTTP and Kafka payloads. It is plugin-based without imposing a DI framework, which keeps the hexagonal layout ours — the domain folder never imports Fastify. Chosen over NestJS for exactly that reason: less framework in the architecture.
Kysely¶
What: A type-safe SQL query builder over the pg driver.
Why: The TS analogue of the sqlc decision: you still think in SQL, but the TypeScript compiler checks tables, columns and result types. No schema DSL competing with the SQL migrations, no ORM entity lifecycle — repositories stay as explicit as on the Go side.
@confluentinc/kafka-javascript¶
What: Confluent's Node client, binding the battle-tested librdkafka engine with a KafkaJS-compatible API.
Why: The historically popular kafkajs is effectively unmaintained; Confluent's
client is actively developed and production-grade. The native module costs a little
image complexity, which is acceptable for getting reliable consumer groups in Node.
tsx + Vitest¶
What: tsx runs TypeScript directly with watch mode for development; Vitest is the
test runner.
Why: Zero-config dev loop (npm run dev) and a fast, TS-native test runner that
shares Vite's transform pipeline with the frontend toolchain.