Architecture overview
Cairn's core follows a strict hexagonal architecture: domain → port → adapter. The dependency rule is the one rule that keeps the codebase clean.
domain ◀── port ◀── usecase adapters ──▶ port (implement)
(pure) (interfaces) (orchestration) (postgres / nats / s3 / connect)
internal/domainimports nothing from port/usecase/adapter. It's pure: types, invariants, the merge engine, segment matching, the visibility model.internal/portdefines driven-port interfaces (it imports only domain).internal/usecaseorchestrates domain + ports — never adapters.- Adapters implement the ports:
internal/adapter/secondary/{postgres, nats,s3,inmem}and the primaryinternal/adapter/primary/connect. cmd/server/wire.gois the only place concrete adapters meet concrete use-cases.
Surfaces
- Connect-RPC + REST — both are live and user-facing, and since 0.6.0
every REST route has a Connect twin on the same use cases, so the two
surfaces behave identically. The Connect server is built on connect-go v2
and speaks the Connect, gRPC and gRPC-Web protocols over HTTP/1.1 and
cleartext HTTP/2 (h2c); each message is capped at 4 MiB. Server streams
(
StreamActivityEvents,StreamNotifications,StreamAdminEvents) carry live events with a 30-second heartbeat. OAuth tokens are checked per scope family on Connect, see OAuth. - Signed content URLs — photos, map snapshots and exports are fetched
with a plain GET from a short-lived URL: presigned on the object store when
CAIRN_STORAGE_PUBLIC_ENDPOINTis set, otherwise served by core itself under/content/{token}. - Web UI — a SvelteKit 3 CSR SPA embedded into the prod binary.
- NATS + JetStream — the async control plane between core and the workers.
Stack
Go 1.27 · connect-go v2 · Postgres 17 + TimescaleDB + PostGIS · pgx/v5 · goose migrations · NATS/JetStream · SvelteKit 3 + Connect-Web + MapLibre + uPlot.
Read on: the ingest pipeline and provider workers.