ARCHITECTURE.mdβ’27.8 kB
# ποΈ Architecture Documentation
## Overview
**Semantic D1 MCP** implements **Domain-Driven Hexagonal Architecture** (Ports and Adapters) to achieve clean separation of concerns, testability, and semantic intent preservation.
This document explains the architectural patterns, layer responsibilities, and design decisions that make this a reference implementation for semantic intent principles.
---
## Table of Contents
- [Hexagonal Architecture](#hexagonal-architecture)
- [Layer Responsibilities](#layer-responsibilities)
- [Domain-Driven Design](#domain-driven-design)
- [Semantic Intent Patterns](#semantic-intent-patterns)
- [Dependency Flow](#dependency-flow)
- [Testing Strategy](#testing-strategy)
---
## Hexagonal Architecture
### Visual Representation
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External World β
β (Claude Desktop, MCP Clients, HTTP Requests) β
βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β (MCP Protocol Adapters) β
β β
β ββββββββββββββββββ ββββββββββββββββββββ β
β β MCPServer β β ToolRegistry β β
β β - Tool defs β β - 6 MCP tools β β
β β - Routing β β - Validation β β
β ββββββββββββββββββ ββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β (Orchestration & Use Cases) β
β β
β ββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β Use Cases β β Handlers β β
β β - AnalyzeSchema β β - MCPToolHandler β β
β β - GetRelations β β - SchemaQueryHandler β β
β β - Optimize β β β β
β ββββββββββββββββββββ βββββββββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DOMAIN LAYER β
β (Business Logic) β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Entities β β
β β - DatabaseSchema (name, environment, tables) β β
β β - TableInfo (columns, indexes, foreign keys) β β
β β - Relationship (from/to tables, semantics) β β
β β - Optimization (recommendations) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Services (Pure Business Logic) β β
β β - SchemaAnalyzer β β
β β - RelationshipAnalyzer β β
β β - OptimizationService β β
β β - ValidationService β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Ports (Interfaces) β β
β β - ICloudflareD1Repository β β
β β - ICacheProvider β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INFRASTRUCTURE LAYER β
β (Technical Implementations) β
β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β Adapters β β HTTP Client β β
β β - CloudflareD1 β β - API calls β β
β β Repository β β - Auth headers β β
β β - InMemoryCache β β - Error handling β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Configuration β β
β β - DatabaseConfig (env-based routing) β β
β β - CloudflareConfig (account, tokens) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External Services β
β (Cloudflare D1 REST API) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
### Why Hexagonal Architecture?
1. **Testability**: Domain logic can be tested without infrastructure
2. **Flexibility**: Swap D1 for PostgreSQL without touching domain code
3. **Semantic Clarity**: Clear boundaries enforce semantic intent preservation
4. **Maintainability**: Changes localized to specific layers
5. **AI-Friendly**: Clear structure helps AI understand intent
---
## Layer Responsibilities
### 1. Domain Layer (Core Business Logic)
**Location**: `src/domain/`
**Responsibility**: Pure business logic independent of infrastructure
**Semantic Intent**: Owns database schema semantics, relationships, optimization rules
#### Entities
**DatabaseSchema**
```typescript
/**
* Semantic Intent: Complete database schema snapshot
*
* Observable Properties:
* - name: Database name from D1
* - environment: Semantic anchor (development/staging/production)
* - tables: Collection of table metadata
* - fetchedAt: Snapshot timestamp
*
* Immutability: Frozen at construction to preserve semantic intent
*/
export class DatabaseSchema {
constructor(
public readonly name: string,
public readonly environment: Environment,
public readonly tables: ReadonlyArray<TableInfo>,
public readonly fetchedAt: Date
) {
Object.freeze(this) // Prevent semantic mutation
}
}
```
**TableInfo**
```typescript
/**
* Semantic Intent: Table structure with semantic markers
*
* Observable Properties:
* - name: Table name from sqlite_master
* - columns: Column definitions (types, constraints)
* - indexes: Performance optimization markers
* - foreignKeys: Semantic relationship markers
*/
export class TableInfo {
hasForeignKey(): boolean {
// Observable: Foreign keys directly visible in schema
return this.foreignKeys.length > 0
}
hasIndexOnColumn(columnName: string): boolean {
// Observable: Index presence is directly observable
return this.indexes.some(idx => idx.columns.includes(columnName))
}
}
```
#### Services (Business Logic)
**SchemaAnalyzer**
```typescript
/**
* Semantic Intent: Pure schema analysis logic
*
* NO infrastructure dependencies:
* - No HTTP calls
* - No D1 API
* - No MCP protocol
*
* Only operates on domain entities
*/
export class SchemaAnalyzer {
analyzeTables(tables: TableInfo[]): AnalysisResult {
// Semantic: Table structure analysis based on observable properties
return {
tableCount: tables.length,
hasPrimaryKeys: tables.every(t => t.hasPrimaryKey()),
foreignKeyCount: tables.reduce((sum, t) => sum + t.foreignKeys.length, 0)
}
}
}
```
**OptimizationService**
```typescript
/**
* Semantic Intent: Generate schema optimization recommendations
*
* Observable Anchoring: Based on schema structure, NOT runtime metrics
*/
export class OptimizationService {
analyzeIndexNeeds(tables: TableInfo[], relationships: Relationship[]): Optimization[] {
const optimizations: Optimization[] = []
// Semantic Rule: Foreign keys SEMANTICALLY need indexes
for (const rel of relationships) {
const table = tables.find(t => t.name === rel.fromTable)
if (table && !table.hasIndexOnColumn(rel.fromColumn)) {
// Observable: Missing index is directly observable
optimizations.push(new Optimization(
'missing_index',
rel.fromTable,
'Foreign key column without index may cause slow joins',
`CREATE INDEX idx_${rel.fromTable}_${rel.fromColumn} ON ${rel.fromTable}(${rel.fromColumn})`,
'high'
))
}
}
return optimizations
}
}
```
#### Ports (Interfaces)
```typescript
/**
* Port: Interface for database access
*
* Semantic Intent: Define domain needs without implementation details
*/
export interface ICloudflareD1Repository {
fetchDatabaseSchema(databaseId: string): Promise<DatabaseSchema>
fetchTableDetails(databaseId: string, tableName?: string): Promise<TableInfo[]>
fetchIndexInformation(databaseId: string): Promise<Index[]>
executeSQLQuery(databaseId: string, sql: string): Promise<QueryResult>
}
```
---
### 2. Application Layer (Orchestration)
**Location**: `src/application/`
**Responsibility**: Coordinate domain services to fulfill use cases
**Semantic Intent**: Preserve intent through transformations
#### Use Cases
**AnalyzeSchemaUseCase**
```typescript
/**
* Use Case: Analyze database schema comprehensively
*
* Intent Preservation: Environment semantic maintained throughout
*/
export class AnalyzeSchemaUseCase {
constructor(
private readonly repository: ICloudflareD1Repository,
private readonly schemaAnalyzer: SchemaAnalyzer,
private readonly cache: ICacheProvider
) {}
async execute(request: AnalyzeSchemaRequest): Promise<SchemaAnalysisResponse> {
// Intent: Environment semantic drives database selection
const databaseId = this.getDatabaseId(request.environment)
// Cache: Performance optimization (10-minute TTL)
const cacheKey = `schema:${request.environment}`
const cached = await this.cache.get<DatabaseSchema>(cacheKey)
if (cached) {
return this.formatResponse(cached, request.includeSamples)
}
// Domain: Fetch and analyze
const schema = await this.repository.fetchDatabaseSchema(databaseId)
const analysis = this.schemaAnalyzer.analyzeTables(schema.tables)
// Cache and return
await this.cache.set(cacheKey, schema, 600)
return this.formatResponse(schema, request.includeSamples)
}
private getDatabaseId(environment: Environment): string {
// Semantic: Environment mapping preserved without override
return this.config.databases[environment].id
}
}
```
#### Handlers
**MCPToolHandler**
```typescript
/**
* Handler: Dispatch MCP tool requests to appropriate use cases
*
* Semantic Intent: Translate MCP protocol to domain operations
*/
export class MCPToolHandler {
async handleToolCall(toolName: string, arguments: unknown): Promise<ToolResult> {
switch (toolName) {
case 'analyze_database_schema':
return this.analyzeSchemaUseCase.execute(arguments as AnalyzeSchemaRequest)
case 'get_table_relationships':
return this.getRelationshipsUseCase.execute(arguments as GetRelationshipsRequest)
// ... other tools
}
}
}
```
---
### 3. Infrastructure Layer (Technical Adapters)
**Location**: `src/infrastructure/`
**Responsibility**: Implement ports using external services/APIs
**Semantic Intent**: Translate external APIs to domain concepts
#### Adapters
**CloudflareD1Repository**
```typescript
/**
* Adapter: Implements ICloudflareD1Repository using D1 REST API
*
* Semantic Intent: Translate D1 API responses to domain entities
* Observable Anchoring: Use API response properties directly
*/
export class CloudflareD1Repository implements ICloudflareD1Repository {
constructor(
private readonly config: CloudflareConfig,
private readonly httpClient: CloudflareAPIClient
) {}
async fetchDatabaseSchema(databaseId: string): Promise<DatabaseSchema> {
// Infrastructure: HTTP call to D1 API
const tables = await this.fetchTableDetails(databaseId)
// Semantic: Preserve environment from config
return new DatabaseSchema(
this.getDatabaseName(databaseId),
this.getEnvironment(databaseId),
tables,
new Date()
)
}
async fetchTableDetails(databaseId: string, tableName?: string): Promise<TableInfo[]> {
// Observable: sqlite_master contains schema semantics
let query = `SELECT * FROM sqlite_master WHERE type='table'`
if (tableName) {
query += ` AND name='${tableName}'`
}
const response = await this.httpClient.query(databaseId, query)
// Translation: API response β Domain entities
return this.parseTableInfo(response.results)
}
private parseTableInfo(apiResults: unknown[]): TableInfo[] {
return apiResults.map(row => {
// Observable: Parse CREATE TABLE statements for semantic structure
const columns = this.parseColumns(row.sql)
const indexes = this.parseIndexes(row.sql)
const foreignKeys = this.parseForeignKeys(row.sql)
return new TableInfo(
row.name,
row.type as 'table' | 'view',
columns,
indexes,
foreignKeys
)
})
}
}
```
**CloudflareAPIClient**
```typescript
/**
* HTTP Client: Cloudflare D1 REST API communication
*
* Responsibilities:
* - Authentication (API tokens)
* - Request formatting
* - Error handling
* - Rate limiting
*/
export class CloudflareAPIClient {
async query(databaseId: string, sql: string): Promise<D1Response> {
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/d1/database/${databaseId}/query`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ sql })
}
)
if (!response.ok) {
throw new CloudflareAPIError(`D1 query failed: ${response.statusText}`)
}
return response.json()
}
}
```
---
### 4. Presentation Layer (MCP Protocol)
**Location**: `src/presentation/`
**Responsibility**: Expose domain capabilities via MCP protocol
**Semantic Intent**: Tool definitions reflect domain operations
#### MCP Server
**MCPServer**
```typescript
/**
* Presentation: MCP Server with tool registration
*
* Semantic Intent: Expose domain capabilities as MCP tools
*/
export class D1DatabaseMCPServer {
private server: Server
constructor(
private readonly analyzeSchemaUseCase: AnalyzeSchemaUseCase,
private readonly getRelationshipsUseCase: GetRelationshipsUseCase,
private readonly validateSchemaUseCase: ValidateSchemaUseCase,
private readonly suggestOptimizationsUseCase: SuggestOptimizationsUseCase
) {
this.server = new Server({
name: 'semantic-d1-mcp',
version: '1.0.0'
}, {
capabilities: { tools: {} }
})
this.registerTools()
}
private registerTools() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'analyze_database_schema',
description: 'Analyze D1 database schema structure and metadata',
inputSchema: {
type: 'object',
properties: {
environment: {
type: 'string',
enum: ['development', 'staging', 'production'],
description: 'Database environment to analyze'
},
include_samples: {
type: 'boolean',
default: true
}
},
required: ['environment']
}
}
// ... 5 more tools
]
}))
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
// Dispatch to use cases
return this.toolHandler.handleToolCall(
request.params.name,
request.params.arguments
)
})
}
}
```
---
## Domain-Driven Design
### Bounded Contexts
#### 1. Schema Context
**Ubiquitous Language**:
- Schema, Table, Column, Constraint, Index
- Primary Key, Foreign Key, Unique Constraint
**Responsibilities**:
- Schema structure analysis
- Constraint validation
- Metadata extraction
**Semantic Anchors**:
- Table semantics defined by `sqlite_master` metadata
- Column semantics from CREATE TABLE statements
#### 2. Relationship Context
**Ubiquitous Language**:
- Relationship, Foreign Key, Reference, Cascade
**Responsibilities**:
- Extract table relationships
- Analyze referential integrity
- Map dependency graphs
**Semantic Anchors**:
- Foreign keys as semantic relationship markers
- Table references preserve original intent
#### 3. Optimization Context
**Ubiquitous Language**:
- Optimization, Recommendation, Index Strategy
**Responsibilities**:
- Analyze missing indexes
- Suggest schema improvements
- Performance recommendations
**Semantic Anchors**:
- Optimizations based on observable schema patterns
- NOT based on runtime query performance (different domain)
#### 4. Environment Context
**Ubiquitous Language**:
- Environment (development, staging, production)
- Database Instance, Configuration
**Responsibilities**:
- Environment-specific database routing
- Configuration management
**Semantic Anchors**:
- Environment semantic preserved through all layers
- No cross-environment data leakage
---
## Semantic Intent Patterns
### 1. Semantic Over Structural
**Rule**: Use observable meaning, not technical characteristics
```typescript
// β
SEMANTIC: Based on foreign key presence (observable)
const needsIndex = table.hasForeignKey() && !table.hasIndexOnForeignKey()
// β STRUCTURAL: Based on row count (derived metric)
const needsIndex = table.rowCount > 10000
```
### 2. Intent Preservation
**Rule**: Maintain semantic contracts through transformations
```typescript
// β
Environment semantic preserved
const schema = await repository.fetchDatabaseSchema(Environment.PRODUCTION)
// Domain layer never overrides "production" intent
// β Intent override violation
if (isQuickAnalysis) {
environment = Environment.DEVELOPMENT // Violates user intent!
}
```
### 3. Observable Anchoring
**Rule**: Base decisions on directly observable properties
```typescript
// β
Observable: Foreign keys visible in schema
const relationships = parseForeignKeys(createTableSQL)
// β Inferred: Guessing from query patterns
const relationships = inferFromQueryLogs(logs)
```
### 4. Immutability Protection
**Rule**: Freeze entities to prevent semantic violations
```typescript
export class DatabaseSchema {
constructor(/* ... */) {
Object.freeze(this) // Prevent accidental mutation
}
}
```
---
## Dependency Flow
### Dependency Inversion Principle
```
Presentation ββdepends onββ> Application ββdepends onββ> Domain
β β β²
β β β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
Infrastructure (implements ports)
```
**Key Point**: All layers depend on Domain (abstractions), not Infrastructure (details)
### Composition Root
**src/index.ts** (Dependency Injection)
```typescript
/**
* Composition Root: Wire all dependencies
*
* Semantic Intent: Single place for dependency graph
*/
// Configuration
const config = loadConfiguration()
// Infrastructure Layer
const apiClient = new CloudflareAPIClient(config.accountId, config.apiToken)
const repository = new CloudflareD1Repository(config, apiClient)
const cache = new InMemoryCacheProvider()
// Domain Layer
const schemaAnalyzer = new SchemaAnalyzer()
const relationshipAnalyzer = new RelationshipAnalyzer()
const optimizationService = new OptimizationService()
// Application Layer
const analyzeSchemaUseCase = new AnalyzeSchemaUseCase(repository, schemaAnalyzer, cache)
const getRelationshipsUseCase = new GetRelationshipsUseCase(repository, relationshipAnalyzer)
const suggestOptimizationsUseCase = new SuggestOptimizationsUseCase(repository, optimizationService)
// Presentation Layer
const mcpServer = new D1DatabaseMCPServer(
analyzeSchemaUseCase,
getRelationshipsUseCase,
suggestOptimizationsUseCase
)
// Start
await mcpServer.start()
```
---
## Testing Strategy
### Layer-Specific Testing
#### Domain Layer Tests (55+ tests)
**Strategy**: Pure unit tests, no mocks needed
```typescript
describe('SchemaAnalyzer', () => {
it('should detect tables without primary keys', () => {
const table = new TableInfo('users', 'table',
[new Column('name', 'TEXT', false)], // no primary key
[], []
)
const analyzer = new SchemaAnalyzer()
const result = analyzer.analyzeTables([table])
expect(result.hasPrimaryKeys).toBe(false)
})
})
```
#### Infrastructure Layer Tests (30+ tests)
**Strategy**: Mock HTTP responses
```typescript
describe('CloudflareD1Repository', () => {
it('should fetch and parse table schema', async () => {
const mockClient = {
query: jest.fn().mockResolvedValue({
results: [{
name: 'users',
type: 'table',
sql: 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)'
}]
})
}
const repository = new CloudflareD1Repository(config, mockClient)
const schema = await repository.fetchDatabaseSchema('test-db-id')
expect(schema.tables).toHaveLength(1)
expect(schema.tables[0].name).toBe('users')
})
})
```
#### Application Layer Tests (16+ tests)
**Strategy**: Mock repository and services
```typescript
describe('AnalyzeSchemaUseCase', () => {
it('should cache schema analysis results', async () => {
const mockCache = new InMemoryCacheProvider()
const useCase = new AnalyzeSchemaUseCase(mockRepository, mockAnalyzer, mockCache)
await useCase.execute({ environment: 'production', includeSamples: true })
await useCase.execute({ environment: 'production', includeSamples: true })
expect(mockRepository.fetchDatabaseSchema).toHaveBeenCalledTimes(1) // Cached!
})
})
```
#### Integration Tests (15+ tests)
**Strategy**: End-to-end with test database
```typescript
describe('End-to-End Schema Analysis', () => {
it('should analyze schema and suggest optimizations', async () => {
const result = await mcpServer.call('analyze_database_schema', {
environment: 'development',
include_samples: true
})
expect(result.tables).toBeDefined()
expect(result.optimizations).toBeDefined()
})
})
```
---
## Benefits of This Architecture
### 1. Testability
- Domain logic tested without HTTP calls
- Mock infrastructure easily
- Fast unit tests
### 2. Flexibility
- Swap D1 for PostgreSQL β only change Infrastructure
- Add new MCP tools β only change Presentation
- Change caching strategy β only change Infrastructure
### 3. Semantic Clarity
- Clear boundaries enforce intent preservation
- Observable properties explicit in domain
- Business rules centralized in services
### 4. Maintainability
- Changes localized to specific layers
- Easy to understand where logic belongs
- Refactoring safe with tests
### 5. AI-Friendly
- Clear structure helps AI understand intent
- Comments explain WHY, not just WHAT
- Semantic patterns consistent throughout
---
## Related Documentation
- [README.md](README.md) - Project overview and quick start
- [D1_MCP_REFACTORING_PLAN.md](D1_MCP_REFACTORING_PLAN.md) - Refactoring roadmap
- [SEMANTIC_ANCHORING_GOVERNANCE.md](SEMANTIC_ANCHORING_GOVERNANCE.md) - Governance rules
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines
---
**This architecture demonstrates how to build maintainable, testable, AI-friendly database tools using semantic intent patterns and hexagonal architecture.** ποΈ