Skip to content

FraiseQL Architecture Decisions

This directory contains the evolution of architectural decisions for FraiseQL, documenting the thinking process and trade-offs for major design choices.

Mutation Response Architecture Evolution

ADR-001: GraphQL Mutation Response - Initial Plan

File: 001_graphql_mutation_response_initial_plan.md Date: 2025-10-16 Status: Superseded by ADR-002

Decision: Create GraphQL-native mutation responses with three-layer transformation (PostgreSQL → Python → Rust → GraphQL).

Context: - Original CDC-style response format incompatible with GraphQL cache normalization - Apollo Client, Relay, URQL require id + __typename for cache updates - Python layer would orchestrate transformation

Why Superseded: - Introduced unnecessary Python parsing layer - User insight: "could there be even more direct path for the data?"


ADR-002: Ultra-Direct Mutation Path

File: 002-ultra-direct-mutation-path.md Date: 2025-10-16 Status: Superseded by ADR-003

Decision: Eliminate Python parsing, use PostgreSQL JSONB::text → Rust → Client directly.

Key Innovation: - Reuse existing query path (RawJSONResult) - PostgreSQL returns JSONB as text string (no Python dict parsing) - Rust transformer handles camelCase + __typename injection - 10-80x faster than Python-based parsing

Why Superseded: - Didn't address CDC event logging requirements - User requirement: "could we still keep debezium compatible logging function?"


ADR-003: Dual-Path Architecture (Ultra-Direct + CDC)

File: 003_dual_path_cdc_pattern.md Date: 2025-10-16 Status: Superseded by ADR-005

Decision: Implement two independent paths within same transaction: - Path A (Client): Ultra-direct PostgreSQL → Rust → Client (~51ms) - Path B (CDC): Async event logging with PERFORM (~1ms, doesn't block client)

Key Innovation: - PostgreSQL PERFORM executes functions asynchronously within transaction - CDC logging doesn't block client response - Both paths maintain ACID guarantees

Architecture:

-- Build response
v_response := build_mutation_response(...);

-- Log CDC event (ASYNC - doesn't block!)
PERFORM log_cdc_event(...);

-- Return immediately
RETURN v_response;

Why Superseded: - Two separate operations (build response + log event) - Risk of divergence between client response and CDC event - User insight: "could we simplify by making the direct client response a part of the CDC event logging?"


ADR-004: Dual-Path Implementation Examples

File: 004_dual_path_implementation_examples.md Date: 2025-10-16 Status: Reference Implementation (Superseded Pattern)

Content: Complete implementation examples of ADR-003 dual-path pattern: - Example 1: Create Customer (simple entity) - Example 2: Update Order (complex entity with validation) - Example 3: Delete Order (with business rules) - Complete CDC event formats - Performance characteristics - Apollo Client cache integration

Value: - Demonstrates thinking process - Shows how dual-path would have worked - Reference for understanding ADR-005 simplification


ADR-005: Simplified Single-Source CDC ✅ CURRENT

File: 005-simplified-single-source-cdc.md Date: 2025-10-16 Status: ✅ ACTIVE - IMPLEMENT THIS

Decision: Store both client response AND CDC data in single event, Rust extracts client_response field.

Key Simplification:

-- Single INSERT with everything
v_event_id := log_mutation_event(
    client_response,  -- What client receives
    before_state,     -- What CDC consumers need
    after_state,      -- What CDC consumers need
    metadata          -- Audit trail
);

-- Return client_response field directly
RETURN (SELECT client_response::text FROM mutation_events WHERE event_id = v_event_id);

Schema:

CREATE TABLE app.mutation_events (
    event_id BIGSERIAL PRIMARY KEY,

    -- What client receives (extracted by Rust)
    client_response JSONB NOT NULL,

    -- What CDC consumers need
    before_state JSONB,
    after_state JSONB,

    -- Audit metadata
    metadata JSONB,
    source JSONB,
    event_timestamp TIMESTAMPTZ DEFAULT NOW(),
    transaction_id BIGINT
);

Benefits: 1. ✅ Single Source of Truth: One INSERT contains everything 2. ✅ Simpler Code: No separate build_mutation_response() helper 3. ✅ Better Audit: CDC log contains exact client response 4. ✅ Same Performance: < 0.1ms overhead for event_id lookup 5. ✅ More Debuggable: Replay exact client responses from CDC log

Trade-offs: - Slightly larger events (~50-100 bytes per mutation) - negligible - Requires SELECT after INSERT - < 0.1ms with PRIMARY KEY lookup

Why This is Final: - Maximum simplicity with no performance cost - Eliminates risk of client response vs CDC data diverging - Perfect audit trail (see exactly what client received) - Natural evolution from ADR-003 dual-path concept


Decision Timeline

ADR-001 (Initial Plan)
   └─→ User: "Use existing Rust transformer, simplify data path"
ADR-002 (Ultra-Direct Path)
   └─→ User: "Could we still keep CDC logging with ultra-fast returns?"
ADR-003 (Dual-Path: Client + CDC)
   └─→ User: "Could we simplify by making client response part of CDC event?"
   └─→ User: "Store exact payload in dedicated field, no conditionals"
ADR-005 (Single-Source CDC) ✅ FINAL

Key Lessons

1. User-Driven Simplification

Each ADR was refined based on user insights: - "Could there be even more direct path?" → Eliminated Python parsing - "Could we keep CDC logging?" → Dual-path pattern - "Could we simplify further?" → Single source of truth

2. Progressive Refinement

  • Started with 3 layers (PostgreSQL → Python → Rust)
  • Eliminated Python layer (PostgreSQL → Rust)
  • Added CDC logging (dual-path)
  • Unified into single source (one INSERT)

3. Performance Maintained Throughout

  • ADR-002: 10-80x faster than Python parsing
  • ADR-003: ~51ms client response (CDC doesn't block)
  • ADR-005: Same performance + simpler code

4. Architecture Drivers

  • GraphQL Cache Compatibility: id + __typename requirement
  • Ultra-Direct Path: Zero Python parsing overhead
  • CDC Event Streaming: Debezium-compatible audit trail
  • Single Source of Truth: Eliminate divergence risk

Implementation Status

  • [x] ADR-001: Documented
  • [x] ADR-002: Documented
  • [x] ADR-003: Documented + Reference implementation created
  • [x] ADR-004: Complete examples documented
  • [x] ADR-005: Designed and documented
  • [ ] ADR-005: Implement new CDC schema
  • [ ] ADR-005: Update mutation functions to use simplified pattern
  • [ ] ADR-005: Implement Python layer (execute_function_raw_json)
  • [ ] ADR-005: Test end-to-end with GraphQL client

Next Steps

  1. Implement ADR-005 simplified CDC schema
  2. Update ecommerce_api mutation functions
  3. Update blog_api mutation functions
  4. Implement Python layer changes
  5. Benchmark performance vs old mutation system
  6. Document CDC consumer patterns