Skip to main content
Version: 0.7.0

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)
  1. internal/domain imports nothing from port/usecase/adapter. It's pure: types, invariants, the merge engine, segment matching, the visibility model.
  2. internal/port defines driven-port interfaces (it imports only domain).
  3. internal/usecase orchestrates domain + ports — never adapters.
  4. Adapters implement the ports: internal/adapter/secondary/{postgres, nats,s3,inmem} and the primary internal/adapter/primary/connect.
  5. cmd/server/wire.go is 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_ENDPOINT is 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.