Skip to content

Architecture Overview

This document describes the technical architecture, system design, and implementation details for ProductGraph.

Current Architecture (Starter)

The Starter architecture uses a single PostgreSQL database with Row-Level Security (RLS) for multi-tenancy. This simplifies operations and is sufficient for ~50M events/month (~1000 paying users).

┌─────────────────────────────────────────────────────────────────────────┐
│                              Clients                                    │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────────┐  │
│   │ @coreforge/  │  │ @omniobserve │  │     Future: Swift/Kotlin     │  │
│   │  telemetry   │  │     /core    │  │                              │  │
│   └──────┬───────┘  └──────┬───────┘  └──────────────┬───────────────┘  │
└──────────┼─────────────────┼─────────────────────────┼──────────────────┘
           │                 │                         │
           └─────────────────┴───────────┬─────────────┘
                                         │ HTTPS
┌─────────────────────────────────────────────────────────────────────────┐
│                     ProductGraph Service (Single Binary)                │
│                                                                         │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────┐  │
│  │ Event Ingestion │  │   GraphQL API   │  │   WebSocket (Future)    │  │
│  │ POST /v1/events │  │                 │  │                         │  │
│  └────────┬────────┘  └────────┬────────┘  └────────────┬────────────┘  │
│           │                    │                        │               │
│           └────────────────────┴────────────────────────┘               │
│                                │                                        │
│                    ┌───────────┴───────────┐                            │
│                    │     Ent ORM Layer     │                            │
│                    │  (Schema & Queries)   │                            │
│                    └───────────┬───────────┘                            │
└────────────────────────────────┼────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│                    PostgreSQL 16+ (Single Instance)                     │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                   Row-Level Security (RLS)                        │  │
│  │              Tenant isolation via org_id policies                 │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌────────────┐   │
│  │    Events    │  │   Sessions   │  │   Journeys   │  │  Projects  │   │
│  │  (BRIN idx)  │  │              │  │              │  │            │   │
│  └──────────────┘  └──────────────┘  └──────────────┘  └────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘

Multi-Tenancy with Row-Level Security

All tenant-scoped tables include an org_id column with RLS policies:

-- Enable RLS on table
ALTER TABLE events ENABLE ROW LEVEL SECURITY;

-- Policy: users can only see their organization's data
CREATE POLICY org_isolation ON events
    USING (org_id = current_setting('app.current_org_id')::uuid);

-- Set org context per request
SET app.current_org_id = 'org-uuid-here';

This approach provides:

  • Security: Tenants cannot access each other's data
  • Simplicity: No application-level filtering required
  • Performance: PostgreSQL optimizes queries with RLS

Technology Stack

Backend

Component Technology Rationale
Language Go 1.22+ Performance, concurrency, strong typing
ORM Ent Type-safe, code-generated, PostgreSQL RLS support
API Framework Chi Lightweight, idiomatic Go router
Database PostgreSQL 16+ All data, RLS for multi-tenancy

Frontend (Planned)

Component Technology Rationale
Framework React 19 Component model, ecosystem
Build Vite 6 Fast builds, ESM
State TanStack Query + Zustand Server state + client state
Canvas React Flow Graph visualization
Charts Apache ECharts Rich analytics charts
Styling Tailwind CSS 4 Utility-first

Data Models

All schemas are defined in Go using Ent and generate PostgreSQL migrations.

Organization

func (Organization) Fields() []ent.Field {
    return []ent.Field{
        field.UUID("id", uuid.UUID{}).Default(uuid.New),
        field.String("name").NotEmpty(),
        field.String("slug").Unique().NotEmpty(),
        field.Time("created_at").Default(time.Now),
        field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now),
    }
}

Project

func (Project) Fields() []ent.Field {
    return []ent.Field{
        field.UUID("id", uuid.UUID{}).Default(uuid.New),
        field.UUID("org_id", uuid.UUID{}),
        field.String("name").NotEmpty(),
        field.String("slug").NotEmpty(),
        field.String("api_key").Unique().NotEmpty(),
        field.JSON("settings", map[string]any{}).Optional(),
        field.Time("created_at").Default(time.Now),
        field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now),
    }
}

Event

func (Event) Fields() []ent.Field {
    return []ent.Field{
        field.UUID("id", uuid.UUID{}).Default(uuid.New),
        field.UUID("org_id", uuid.UUID{}),           // RLS column
        field.UUID("project_id", uuid.UUID{}),
        field.String("session_id").NotEmpty(),
        field.String("user_id").Optional(),
        field.Enum("event_type").Values(
            "page_view", "page_leave", "ui_click", "ui_input",
            "ui_scroll", "ui_submit", "state_change",
            "api_request", "api_response", "journey_step",
            "error", "performance", "custom",
        ),
        field.Time("timestamp"),
        // ... additional fields
    }
}

func (Event) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("org_id", "project_id", "timestamp"),
        index.Fields("org_id", "session_id"),
        index.Fields("org_id", "journey_id"),
    }
}

Performance Targets

Metric Target Measurement
Event ingestion latency < 100ms p99 API response time
Query latency (simple) < 200ms p99 GraphQL response
Query latency (complex) < 2s p99 GraphQL response
Canvas render < 500ms Initial load

Security

Authentication

  • API Keys: Project-scoped keys for event ingestion
  • JWT Tokens: User authentication for dashboard (planned)
  • OAuth 2.0: GitHub, Google SSO (planned)

Data Protection

  • Row-Level Security: Tenant isolation at database level
  • Encryption: TLS 1.3 in transit
  • PII Redaction: Configurable field scrubbing (planned)

Analytics Integration (v0.2.0)

ProductGraph forwards events to external analytics providers via omnidxi:

Event Ingestion
┌─────────────────────────────────────────────────────────┐
│                    MultiPublisher                        │
│                                                          │
│   ┌─────────────────┐        ┌─────────────────────────┐│
│   │ Memory Publisher│        │   Analytics Adapter     ││
│   │   (PostgreSQL)  │        │      (omnidxi)          ││
│   └─────────────────┘        └───────────┬─────────────┘│
└──────────────────────────────────────────┼──────────────┘
                              ┌────────────┴────────────┐
                              ▼                         ▼
                       ┌───────────┐             ┌───────────┐
                       │ Amplitude │             │  Mixpanel │
                       └───────────┘             └───────────┘

Key features:

  • Backend-first: Server-side forwarding bypasses ad blockers
  • Multi-provider: Send to Amplitude and Mixpanel simultaneously
  • Zero frontend changes: Existing SDK integration works as-is
  • Unified schema: OTel-compatible events translate automatically

See the Analytics Integration Guide for configuration details.

Future Architecture

See the Scaling Guide for:

  • Growth architecture (+Kafka, ClickHouse, Redis)
  • Scale architecture (sharded, multi-region)
  • Migration paths and cost analysis