For Architects

Design systems with confidence

Your Challenge

Building scalable systems requires clean architecture. Most GraphQL frameworks compromise on fundamentals:

  • Resolvers blur the line between business logic and data access
  • N+1 queries are a surprise (happens at scale)
  • Caching logic spreads across application code
  • No clear separation between queries and mutations
  • Difficult to reason about data flow and dependencies

You end up with tangled systems that are hard to scale and maintain.

Database-First Architecture

Clear Separation of Concerns

FraiseQL enforces architectural boundaries:

┌─────────────────────────────────────┐
API Layer (schema.json → Rust)     │
- Routing & serialization          │
- Authentication                   │
- Query compilation                │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│  Read Layer (SQL Views, v_*)        │
- Data composition                 │
- Relationships                    │
- Joins (one query, always)        │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│  Write Layer (SQL Functions, fn_*)  │
- Business logic                   │
- Validation & constraints         │
- Side effects & triggers          │
└─────────────────────────────────────┘

Business logic lives in the database, not the API.

Result: System is predictable, testable, scalable

CQRS at Architecture Level

Queries and Mutations are fundamentally different:

Aspect Queries (Reads) Mutations (Writes)
Pattern Read from pre-composed views Write to base tables
Consistency Eventually consistent (fine for reads) Strongly consistent (critical for writes)
Scaling Read replicas (unlimited scale) Single writer + replicas (write bottleneck)
Optimization Views + indexes Constraints + triggers
Caching Cache entire view (safe) Careful invalidation (risky)

Treating them separately enables better architecture

Design Patterns Enabled by FraiseQL

Pattern 1: Event Sourcing Compatible

Views naturally work with event-driven architecture:

Events (immutable log)

State (computed from events)

Views (composed from state)

GraphQL queries (read from views)

This is the CQRS pattern:
- Command side: Write events
- Query side: Read from views

Pattern 2: Multi-Tenant Architecture

Views make multi-tenancy clean:

CREATE VIEW v_user_for_tenant AS
SELECT ...
FROM tb_user
WHERE tenant_id = current_tenant_id;

Each tenant gets their own view (or filtered).
No special logic in application.
Data isolation at database level.

Pattern 3: API Versioning

Different GraphQL versions, same database:

v1.0: Use v_user (old schema)
v1.1: Use v_user + new fields (backward compatible)
v2.0: Use v_user_v2 (new schema, backward incompatible)

Old clients use old views.
New clients use new views.
Database handles both. Database is source of truth.

Pattern 4: Read Model Generation

Generate reporting APIs without touching OLTP:

OLTP Database (production transactional DB)

ETL / Replication

Reporting DB (or data warehouse)

FraiseQL Views (for reporting API)

GraphQL Reporting API

Separate read model, no OLTP impact.

Scaling Architecture

Horizontal Scaling

Application is stateless, scales horizontally:

Request Load Balancer
  ├─ App 1
  ├─ App 2
  ├─ App 3
  └─ App N

   Database

Any app can handle any request.
No shared state between apps.
Add/remove apps at will.

Read Scaling (Replicas)

Reads scale independently from writes:

Primary Database (writes)

Read Replica 1 (reads)
Read Replica 2 (reads)
Read Replica 3 (reads)

Views work on replicas.
Queries automatically distributed.
Writes go to primary.

Vertical Scaling (Database)

When database becomes bottleneck, you know it:

  • ✅ Add more memory (cache queries)
  • ✅ Add more CPU (parallel execution)
  • ✅ Add better storage (SSD optimization)
  • ✅ Add indexes (query optimization)

No guessing. Database metrics tell you exactly what to do.

Data Tier Scaling

Database itself can scale:

  • Read replicas (geographic distribution)
  • Connection pooling (efficient connections)
  • Sharding (if needed, but views hide complexity)
  • Materialized views (pre-compute heavy aggregations)

Reliability & Resilience

Fail-Safe Defaults

FraiseQL architecture prevents common failure modes:

Failure Mode Traditional FraiseQL
Cache stampede Many queries simultaneously Stateless app, no cache state
Resolver cascade One slow resolver blocks others One query, no blocking
Connection leak ORM + resolver complexity Standard DB connections
N+1 explosion Database overload (surprise) Caught at compile time
Stale data Cache invalidation bugs Views always reflect current state

Circuit Breaker Pattern

Implement at application level (not inside framework):

@fraiseql.query(sql_source="v_expensive")
def expensive() -> list[Data]:
  pass  # Backed by v_expensive view

Integration Patterns

Microservices

Each service has its own database and GraphQL API:

User Service      Order Service     Product Service
    ↓                  ↓                  ↓
v_user           v_order              v_product
    ↓                  ↓                  ↓
GraphQL API      GraphQL API          GraphQL API

Federation / Apollo can compose across services.
Each service owns its data and views.

API Gateway

Use standard API gateway in front:

  • ✅ Rate limiting (at gateway)
  • ✅ Authentication (at gateway)
  • ✅ Request transformation (at gateway)
  • ✅ GraphQL queries (reach your FraiseQL API)

Data Warehouse Integration

Two databases, different purposes:

OLTP DB (production, FraiseQL)

ETL / Stream

Data Warehouse (analytics, big queries)

BI Tools, Dashboards

FraiseQL handles operational queries (fast, small).
Data warehouse handles analytical queries (slow, big).

Architectural Trade-Offs

Storage vs Compute

FraiseQL trades storage for compute simplicity:

  • ✅ Denormalized JSONB in views (2-4x storage)
  • ✅ One query per request (1x compute)
  • ✅ No resolvers (0 app logic complexity)
  • ❌ Storage cost higher than normalized schema

Modern storage is cheap. Complexity is expensive. This is the right trade-off.

Flexibility vs Predictability

FraiseQL chooses predictability:

  • ✅ Every query is compiled and validated
  • ✅ Performance is predictable
  • ✅ No runtime interpretation
  • ❌ Cannot generate ad-hoc queries at runtime

For production APIs, predictability > flexibility.

Architectural Metrics

What Improves with FraiseQL

Metric Typical Gain
Query complexity (cyclomatic) ↓ 60-70%
Number of resolvers ↓ 80%
Database queries per request ↓ 95%
Request latency (p99) ↓ 70-80%
Operational complexity ↓ 50%
Onboarding time for new developers ↓ 40%

Design with Confidence