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% |