Skip to main content

Architecture Overview

GospeLib starts as a solo-founder product and must eventually support tens of thousands of subscribers. The architecture accomplishes two competing goals simultaneously: be buildable by one person on day one, and never require a rewrite to reach enterprise scale.

The failure mode to avoid is "startup architecture" — the pattern where early decisions bake in assumptions that force a complete rewrite when the product achieves traction. Instead, we adopt evolutionary architecture: add complexity only where it's earned, but design every seam so that complexity can be added without breaking what exists.

Core Principles

Principle 1: Language-Right, Not TypeScript-by-Default

TypeScript is the right choice for UI code, SDK code, and Node-based tooling. It is not the right choice for a high-throughput API gateway, a data ingestion pipeline, or AI feature development. Each service uses the language whose ecosystem best matches its problem domain.

LanguageServicesWhy
GoGateway, Auth, Billing, NotificationsNative concurrency, sub-ms overhead, tiny Docker images
PythonContent, AI, IngestBest graph DB client ecosystem, AI library access, data transformation
TypeScriptWeb, Admin, Mobile, SDK, UIReact/Next.js ecosystem, shared types across frontends

Principle 2: Shared Infrastructure, Independent Deployability

Services share infrastructure definitions, config schemas, CI/CD patterns, logging conventions, and error formats — but each service can be built, tested, and deployed independently. No service imports code from another service at runtime. Shared logic lives in explicitly versioned packages.

Principle 3: Seams Before Premature Splitting

On day one, services that are logically distinct may run in the same process. But they are authored as separate modules with their own API contracts from the start. When traffic demands it, they split into separate containers without changing any public interface.

Principle 4: Infrastructure as Code, From Day One

Even local development runs through Docker Compose driven by the same Terraform variable definitions used in production. There is no "I'll add IaC later."

Principle 5: Observability Is Not Optional

Every service emits structured JSON logs, metrics in OpenTelemetry format, and distributed traces with correlation IDs. This is wired up at project init, not added when production breaks.

Principle 6: Data at the Boundary

Services communicate through well-typed contracts (OpenAPI). Internal data models are never exposed directly. Schema versioning is explicit. This makes internal refactoring safe.

Principle 7: The Graph Is the Product

FalkorDB is not a cache or secondary store — it is the product. Every architectural decision about performance, caching, and query design serves the health of the graph. PostgreSQL handles operational data (users, subscriptions, audit logs) that does not belong in the graph.

The Three Phases

Decisions made in Phase 1 must not block Phase 2 or Phase 3.

PhaseTimelineScaleKey Constraints
Phase 1: Solo/SeedMonths 0–181–3 engineers, 0–10K usersSpeed of execution, minimal ops overhead
Phase 2: GrowthMonths 18–483–12 engineers, 10K–100K usersTeam coordination, service independence, horizontal scale
Phase 3: EnterpriseMonth 48+12+ engineers, 100K+ usersCompliance, SLA, multi-region, data residency

Phase 1 may collapse multiple services into one process; Phase 2 and 3 split them. The code is the same either way.

graph LR
P1["Phase 1<br/>Docker Compose<br/>Single node<br/>$150-300/mo"] --> P2["Phase 2<br/>EKS Cluster<br/>Auto-scaling<br/>$1.5-3K/mo"]
P2 --> P3["Phase 3<br/>Multi-region<br/>Service mesh<br/>Enterprise SLAs"]

High-Level Architecture

graph TB
subgraph Clients
Web["Web App<br/>(Next.js)"]
Mobile["Mobile App<br/>(Expo)"]
Admin["Admin Dashboard<br/>(Next.js)"]
end

subgraph Gateway Layer
GW["API Gateway<br/>(Go/Chi)"]
end

subgraph Services
Content["Content Service<br/>(Python/FastAPI)"]
Auth["Auth Service<br/>(Go)"]
Billing["Billing Service<br/>(Go)"]
AI["AI Service<br/>(Python/FastAPI)"]
Notif["Notifications<br/>(Go)"]
end

subgraph Data Stores
FDB["FalkorDB<br/>(Graph DB)"]
PG["PostgreSQL<br/>(+ pgvector)"]
Redis["Redis<br/>(Cache/Queue)"]
TS["Typesense<br/>(Search)"]
end

Web & Mobile & Admin --> GW
GW --> Content & Auth & Billing & AI & Notif
Content --> FDB & Redis & TS
Auth --> PG & Redis
Billing --> PG & Redis
AI --> PG & Redis
Notif --> PG & Redis

What Does NOT Change Across Phases

  • API contracts — versioned from day 1
  • Service boundaries — drawn correctly from day 1
  • Database schemas — migrate forward, never backward
  • Frontend code — scales independently via CDN
  • Ingest pipeline — runs as a job, not a server