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.
n::: tip Research Foundation PerchIQX implements Hexagonal Architecture with semantic intent - where domain logic is expressed through intent-rich natural language that serves as both documentation and implementation guide. This approach treats semantic meaning as architectural infrastructure.
📚 Read the foundational research: Semantic Intent as Single Source of Truth :::
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
// 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 analysisGetRelationshipsUseCase
- Extract relationshipsValidateSchemaUseCase
- Run schema validationSuggestOptimizationsUseCase
- Generate recommendations
Key Principles:
- Use case per tool
- Coordinate domain services
- No business logic (delegate to domain)
// 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 aggregateTableInfo
- Table metadataColumn
- Column definitionForeignKey
- Foreign key relationshipIndex
- Index definitionRelationship
- Analyzed relationship
Services:
SchemaAnalyzer
- Schema introspection logicRelationshipAnalyzer
- Relationship extractionOptimizationService
- Recommendation engine
Key Principles:
- No infrastructure dependencies
- Semantic validation
- Observable property anchoring
// 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 accessCloudflareAPIClient
- HTTP clientInMemoryCacheProvider
- Response caching- Configuration classes
Key Principles:
- Implement domain interfaces (ports)
- Hide external API details
- Handle technical concerns (retry, caching)
// 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:
- Domain has no dependencies
- Application depends only on domain
- Infrastructure implements domain interfaces
- Presentation uses application use cases
Semantic Intent Principles
1. Semantic Over Structural
Decisions based on meaning, not metrics:
// ✅ 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:
// ✅ 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:
// ✅ 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:
// 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:
// 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:
// 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:
class SchemaAnalyzer {
analyzeSchema(schema: DatabaseSchema): SchemaAnalysis {
// Pure domain logic
}
}
Value Objects
Immutable semantic values:
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
- Hexagonal Architecture - Deep dive into ports and adapters
- Semantic Intent Pattern - Research foundations
- Domain Model - Entity design principles
- ARCHITECTURE.md - Complete architecture docs