Skip to content

Architecture

PerchIQX is built on Hexagonal Architecture (Ports and Adapters) with a strong emphasis on Domain-Driven Design and Semantic Intent patterns. This architecture ensures maintainability, testability, and semantic clarity.

Architectural Overview

┌─────────────────────────────────────────────────────────┐
│                   Presentation Layer                     │
│              (MCP Server - Protocol Handling)            │
│                                                           │
│  - Tool registration and routing                         │
│  - Request/response transformation                       │
│  - MCP protocol compliance                               │
└────────────────────┬────────────────────────────────────┘

┌────────────────────▼────────────────────────────────────┐
│                  Application Layer                       │
│        (Use Cases - Schema Analysis Orchestration)      │
│                                                           │
│  - AnalyzeSchemaUseCase                                  │
│  - GetRelationshipsUseCase                               │
│  - ValidateSchemaUseCase                                 │
│  - SuggestOptimizationsUseCase                           │
└────────────────────┬────────────────────────────────────┘

┌────────────────────▼────────────────────────────────────┐
│                    Domain Layer                          │
│     (Schema Entities, Relationship Logic, Services)     │
│                  Pure Business Logic                     │
│                                                           │
│  Entities:                                               │
│  - DatabaseSchema, TableInfo, Column                     │
│  - ForeignKey, Index, Relationship                       │
│                                                           │
│  Services:                                               │
│  - SchemaAnalyzer                                        │
│  - RelationshipAnalyzer                                  │
│  - OptimizationService                                   │
└────────────────────┬────────────────────────────────────┘

┌────────────────────▼────────────────────────────────────┐
│                Infrastructure Layer                      │
│       (Cloudflare D1 REST API, HTTP Client)             │
│                Technical Adapters                        │
│                                                           │
│  - CloudflareD1Repository (data access)                  │
│  - CloudflareAPIClient (HTTP)                            │
│  - InMemoryCacheProvider (caching)                       │
└─────────────────────────────────────────────────────────┘

Layer Responsibilities

1. Presentation Layer

Purpose: Handle MCP protocol communication

Components:

  • D1DatabaseMCPServer - Main MCP server class
  • Tool handlers for each MCP tool
  • Request validation and response formatting

Key Principles:

  • Protocol-agnostic domain layer
  • Thin translation layer
  • No business logic
typescript
// src/presentation/mcp/MCPServer.ts
export class D1DatabaseMCPServer {
  async handleAnalyzeSchema(params: AnalyzeSchemaParams) {
    // 1. Validate MCP request
    // 2. Call use case
    // 3. Format MCP response
  }
}

2. Application Layer

Purpose: Orchestrate domain services to fulfill use cases

Components:

  • AnalyzeSchemaUseCase - Coordinate schema analysis
  • GetRelationshipsUseCase - Extract relationships
  • ValidateSchemaUseCase - Run schema validation
  • SuggestOptimizationsUseCase - Generate recommendations

Key Principles:

  • Use case per tool
  • Coordinate domain services
  • No business logic (delegate to domain)
typescript
// src/application/use-cases/AnalyzeSchemaUseCase.ts
export class AnalyzeSchemaUseCase {
  async execute(environment: Environment, options: AnalyzeOptions) {
    // 1. Fetch schema from repository
    // 2. Analyze via SchemaAnalyzer service
    // 3. Return domain result
  }
}

3. Domain Layer

Purpose: Pure business logic with no external dependencies

Entities:

  • DatabaseSchema - Root aggregate
  • TableInfo - Table metadata
  • Column - Column definition
  • ForeignKey - Foreign key relationship
  • Index - Index definition
  • Relationship - Analyzed relationship

Services:

  • SchemaAnalyzer - Schema introspection logic
  • RelationshipAnalyzer - Relationship extraction
  • OptimizationService - Recommendation engine

Key Principles:

  • No infrastructure dependencies
  • Semantic validation
  • Observable property anchoring
typescript
// src/domain/entities/TableInfo.ts
export class TableInfo {
  // Semantic validation
  hasPrimaryKey(): boolean {
    return this.columns.some(c => c.isPrimaryKey);
  }

  // Observable anchoring
  getForeignKeys(): ForeignKey[] {
    return this.foreignKeys;  // Direct observation
  }
}

4. Infrastructure Layer

Purpose: External system integration (Cloudflare D1 API)

Components:

  • CloudflareD1Repository - D1 data access
  • CloudflareAPIClient - HTTP client
  • InMemoryCacheProvider - Response caching
  • Configuration classes

Key Principles:

  • Implement domain interfaces (ports)
  • Hide external API details
  • Handle technical concerns (retry, caching)
typescript
// src/infrastructure/adapters/CloudflareD1Repository.ts
export class CloudflareD1Repository implements ICloudflareD1Repository {
  async fetchSchema(environment: Environment): Promise<DatabaseSchema> {
    // 1. Call Cloudflare D1 REST API
    // 2. Transform to domain entities
    // 3. Return DatabaseSchema
  }
}

Dependency Flow

Presentation → Application → Domain ← Infrastructure

                        (Interfaces only)

Key Rules:

  1. Domain has no dependencies
  2. Application depends only on domain
  3. Infrastructure implements domain interfaces
  4. Presentation uses application use cases

Semantic Intent Principles

1. Semantic Over Structural

Decisions based on meaning, not metrics:

typescript
// ✅ SEMANTIC: Based on observable schema properties
const needsIndex = table.hasForeignKey() && !table.hasIndexOnForeignKey();

// ❌ STRUCTURAL: Based on technical metrics
const needsIndex = table.rowCount > 10000 && table.queryCount > 100;

2. Intent Preservation

Environment semantics maintained through transformations:

typescript
// ✅ Environment intent preserved
const schema = await fetchSchema(Environment.PRODUCTION);
// Analysis preserves "production" context - no overrides

// ❌ Intent lost through transformation
const schema = await fetchSchema("prod"); // String loses semantic meaning

3. Observable Anchoring

Decisions anchored to directly observable properties:

typescript
// ✅ Based on observable schema markers
const relationships = extractForeignKeys(sqliteMaster);

// ❌ Based on inferred behavior
const relationships = inferFromQueryPatterns(logs);

Testing Strategy

398 tests across all layers:

  • Domain Layer: 212 tests (entities, services, validation)
  • Infrastructure Layer: 64 tests (D1 adapter, API client, config)
  • Application Layer: 35 tests (use cases, orchestration)
  • Presentation Layer: 13 tests (MCP server, tool routing)
  • Integration: 15 tests (end-to-end flows)
  • Value Objects: 59 tests (Environment, immutability)

Test Isolation

Each layer tested independently:

typescript
// Domain tests - no mocks needed (pure logic)
describe('TableInfo', () => {
  it('detects missing primary key', () => {
    const table = new TableInfo('users', [/* columns */]);
    expect(table.hasPrimaryKey()).toBe(false);
  });
});

// Infrastructure tests - mock HTTP client
describe('CloudflareD1Repository', () => {
  it('fetches schema from API', async () => {
    mockAPIClient.get.mockResolvedValue(mockResponse);
    const schema = await repository.fetchSchema(Environment.DEV);
    expect(schema).toBeInstanceOf(DatabaseSchema);
  });
});

Directory Structure

src/
├── domain/              # Business logic (entities, services)
│   ├── entities/        # DatabaseSchema, TableInfo, Column, etc.
│   ├── services/        # SchemaAnalyzer, RelationshipAnalyzer, etc.
│   ├── repositories/    # Port interfaces
│   └── value-objects/   # Environment enum
├── application/         # Use cases and orchestration
│   ├── use-cases/       # AnalyzeSchema, GetRelationships, etc.
│   └── ports/           # Cache provider interface
├── infrastructure/      # External adapters
│   ├── adapters/        # CloudflareD1Repository, Cache
│   ├── config/          # CloudflareConfig, DatabaseConfig
│   └── http/            # CloudflareAPIClient
├── presentation/        # MCP protocol layer
│   └── mcp/             # D1DatabaseMCPServer
└── index.ts             # Composition root (DI)

Composition Root

Dependency Injection at application startup:

typescript
// src/index.ts
async function main() {
  // Infrastructure Layer
  const apiClient = new CloudflareAPIClient(config);
  const repository = new CloudflareD1Repository(apiClient, dbConfig);
  const cache = new InMemoryCacheProvider();

  // Domain Services
  const schemaAnalyzer = new SchemaAnalyzer();
  const relationshipAnalyzer = new RelationshipAnalyzer();

  // Application Use Cases
  const analyzeSchemaUseCase = new AnalyzeSchemaUseCase(
    repository, schemaAnalyzer, cache
  );

  // Presentation Layer
  const mcpServer = new D1DatabaseMCPServer(
    analyzeSchemaUseCase,
    // ... other use cases
  );

  await mcpServer.start();
}

Design Patterns

Repository Pattern

Abstract data access behind interface:

typescript
// Domain defines interface
interface ICloudflareD1Repository {
  fetchSchema(env: Environment): Promise<DatabaseSchema>;
}

// Infrastructure implements
class CloudflareD1Repository implements ICloudflareD1Repository {
  // Implementation details
}

Service Layer

Business logic in domain services:

typescript
class SchemaAnalyzer {
  analyzeSchema(schema: DatabaseSchema): SchemaAnalysis {
    // Pure domain logic
  }
}

Value Objects

Immutable semantic values:

typescript
enum Environment {
  DEVELOPMENT = "development",
  STAGING = "staging",
  PRODUCTION = "production"
}

Key Benefits

1. Testability

  • Pure domain logic (no mocks needed)
  • Interface-based infrastructure (easy to mock)
  • Independent layer testing

2. Maintainability

  • Clear separation of concerns
  • Semantic clarity in domain
  • Easy to locate and modify logic

3. Flexibility

  • Swap infrastructure (D1 → PostgreSQL)
  • Change protocols (MCP → REST API)
  • Domain remains unchanged

4. AI-Friendly

  • Semantic intent clear in code
  • Natural language mapping to use cases
  • Observable properties for reasoning

Further Reading