Layer Boundaries
This document defines the layer boundaries and responsibilities within the ReX architecture, ensuring clear separation of concerns and maintainable code organization.
Architectural Layer Overview
ReX follows a layered architecture with clear boundaries between components:
┌─────────────────────────────────────────┐
│ Application Layer │
│ (React Components, Hooks, Views) │
└───────────────────┬─────────────────────┘
▼
┌─────────────────────────────────────────┐
│ Store Layer │
│ (ContentStore, Registry, API) │
└───────────────────┬─────────────────────┘
▼
┌─────────────────────────────────────────┐
│ Middleware Layer │
│ (Validation, Caching, Logging, Plugins) │
└───────────────────┬─────────────────────┘
▼
┌─────────────────────────────────────────┐
│ Adapter Layer │
│ (Storage Abstraction, Normalization) │
└───────────────────┬─────────────────────┘
▼
┌─────────────────────────────────────────┐
│ Storage Layer │
│ (Physical Persistence, External Systems)│
└─────────────────────────────────────────┘
Each layer has specific responsibilities and interfaces that define how it communicates with adjacent layers.
Application Layer
The Application Layer represents the user-facing components and interfaces of the system.
Responsibilities
- Rendering content in the user interface
- Handling user interactions and input
- Managing local component state
- Implementing application-specific business logic
- Invoking content operations on the Store Layer
Key Components
- React Components: UI components that render content
- React Hooks: Custom hooks for accessing content system
- View Controllers: Components that coordinate data and presentation
- State Management: Local and global state management
Interface Boundaries
The Application Layer communicates with the Store Layer through:
// React hooks for content access
function useContent(uri: string): {
content: Content | null
loading: boolean
error: Error | null
}
function useContentWrite(uri: string): {
write: (content: Content) => Promise<void>
status: 'idle' | 'loading' | 'success' | 'error'
error: Error | null
}
// Content components
function ContentDisplay({
uri,
fallback,
components,
transformers,
}): ReactElement
function ContentEditor({ uri, onSave, components, plugins }): ReactElement
Store Layer
The Store Layer provides a high-level API for content operations and coordinates access to the content system.
Responsibilities
- Providing a unified API for content operations
- Coordinating between components and storage
- Managing content registry and resolution
- Handling content operation lifecycle
- Dispatching events for content changes
Key Components
- ContentStore: Main facade for content operations
- ContentRegistry: Registry of content resources
- Operation Coordinator: Coordinates complex operations
- Event Dispatcher: Manages content change notifications
- Store Factory: Creates configured store instances
Interface Boundaries
The Store Layer communicates with the Application Layer through:
interface ContentStore {
// Core operations
read(uri: string, options?: ReadOptions): Promise<Content>
write(uri: string, content: Content, options?: WriteOptions): Promise<void>
delete(uri: string, options?: DeleteOptions): Promise<void>
list(pattern: string, options?: ListOptions): Promise<string[]>
exists(uri: string, options?: ExistsOptions): Promise<boolean>
// Event handling
onChange(listener: ChangeListener): Unsubscribe
watch(pattern: string, listener: WatchListener): Unsubscribe
// Resource management
dispose(): Promise<void>
}
The Store Layer communicates with the Middleware Layer through:
interface MiddlewareContext {
operation: ContentOperation
uri: string
content?: Content
options: OperationOptions
store: ContentStore
result?: any
next: () => Promise<any>
}
type Middleware = (context: MiddlewareContext) => Promise<any>
Middleware Layer
The Middleware Layer implements cross-cutting concerns and content processing.
Responsibilities
- Validating content against schemas
- Implementing caching strategies
- Logging operations and errors
- Transforming content between formats
- Providing extension points for plugins
- Managing cross-cutting concerns
Key Components
- Middleware Pipeline: Processes content operations sequentially
- Content Validators: Validate content against schemas
- Content Transformers: Transform content between formats
- Caching Middleware: Implement caching strategies
- Logging Middleware: Log operations and errors
- Error Handling Middleware: Handle and recover from errors
Interface Boundaries
The Middleware Layer communicates with the Store Layer through:
// Middleware composition
function pipe<T>(...middlewares: Middleware[]): Middleware
// Middleware factory
function createMiddleware(options: MiddlewareOptions): Middleware
// Common middleware
function withValidation(schema: ContentSchema): Middleware
function withCaching(options: CacheOptions): Middleware
function withLogging(options: LogOptions): Middleware
function withTransformation(transforms: ContentTransforms): Middleware
The Middleware Layer communicates with the Adapter Layer through:
// Adapter access within middleware
async function processOperation(context: MiddlewareContext) {
const { operation, uri, content, adapter } = context
// Pre-processing
const processedContent = preProcess(content)
// Delegate to adapter
const result = await adapter[operation](uri, processedContent)
// Post-processing
return postProcess(result)
}
Adapter Layer
The Adapter Layer provides a unified interface to different storage backends.
Responsibilities
- Abstracting storage mechanisms (filesystem, database, API)
- Converting between storage formats and Content objects
- Normalizing paths and URIs across storage systems
- Handling environment-specific storage access
- Providing consistent error handling
Key Components
- Content Adapters: Implement storage operations
- Adapter Factory: Creates adapter instances
- URI Resolver: Resolves and normalizes URIs
- Content Serializer: Converts between formats
- Error Mapper: Maps storage errors to content errors
Interface Boundaries
The Adapter Layer communicates with the Middleware Layer through:
interface ContentAdapter {
// Core operations
read(uri: string): Promise<Content>
write(uri: string, content: Content): Promise<void>
delete(uri: string): Promise<void>
list(pattern: string): Promise<string[]>
exists(uri: string): Promise<boolean>
// Optional capabilities
watch?(pattern: string, listener: WatchListener): Unsubscribe
getMetadata?(uri: string): Promise<ContentMetadata>
createDirectory?(uri: string): Promise<void>
copy?(sourceUri: string, targetUri: string): Promise<void>
move?(sourceUri: string, targetUri: string): Promise<void>
// Resource management
dispose(): Promise<void>
// Capability checks
capabilities: AdapterCapabilities
}
The Adapter Layer communicates with the Storage Layer through storage-specific APIs:
// Filesystem adapter example
async function readFromFilesystem(path: string): Promise<Content> {
// Convert path to filesystem path
const fsPath = resolvePath(path)
// Read file with Node.js fs module
const data = await fs.promises.readFile(fsPath, 'utf8')
const stats = await fs.promises.stat(fsPath)
// Extract metadata from file stats
const metadata = {
createdAt: stats.birthtime,
updatedAt: stats.mtime,
// Other metadata from filesystem or content
}
// Determine content type from extension
const contentType = determineContentType(fsPath)
// Return as Content object
return {
data,
contentType,
metadata,
}
}
Storage Layer
The Storage Layer represents the physical storage systems and external resources.
Responsibilities
- Physical storage and retrieval of content
- Raw data access (files, databases, APIs)
- Environment-specific storage operations
- Connection management for external resources
- Low-level error handling and reporting
Key Components
- Filesystem: Local file system access
- IndexedDB: Browser persistent storage
- LocalStorage: Simple browser storage
- HTTP Client: Remote API access
- Database Clients: Database access
Interface Boundaries
The Storage Layer interacts with physical storage and network through:
// Node.js filesystem example
const fs = require('fs/promises')
// Read file
async function readFile(path: string): Promise<string> {
return fs.readFile(path, 'utf8')
}
// Write file
async function writeFile(path: string, data: string): Promise<void> {
await fs.mkdir(path.dirname(path), { recursive: true })
return fs.writeFile(path, data, 'utf8')
}
// Browser IndexedDB example
function openDatabase(name: string): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, 1)
request.onerror = () => reject(request.error)
request.onsuccess = () => resolve(request.result)
// Handle database upgrade
request.onupgradeneeded = event => {
const db = request.result
db.createObjectStore('content', { keyPath: 'uri' })
}
})
}
Layer Communication Rules
To maintain clean architecture, the following rules govern layer communication:
- Adjacent Layer Communication: Layers should only communicate with adjacent layers
- Dependency Direction: Dependencies flow downward (upper layers depend on lower layers)
- Interface Abstraction: Layers communicate through well-defined interfaces
- Encapsulation: Implementation details are encapsulated within layers
- Capability Declaration: Layers declare their capabilities to adjacent layers
Environment-Specific Layer Adaptations
The layer boundaries adapt to different runtime environments:
Node.js (Server)
- Storage Layer: Uses Node.js filesystem (fs module)
- Adapter Layer: Implements FileSystemAdapter
- Application Layer: May use server-side rendering
Browser
- Storage Layer: Uses IndexedDB, localStorage, fetch API
- Adapter Layer: Implements IndexedDBAdapter, LocalStorageAdapter, HttpAdapter
- Application Layer: React components in browser DOM
Isomorphic (Universal)
- Store Layer: Same API across environments
- Middleware Layer: Same pipeline across environments
- Adapter Layer: Environment-specific adapter implementations
- Helper Functions: Environment detection and adaptation
Layer Testing Strategy
Each layer has a specific testing approach:
- Application Layer: Component tests with React Testing Library
- Store Layer: Integration tests of store operations
- Middleware Layer: Unit tests for individual middleware
- Adapter Layer: Integration tests with mock storage
- Storage Layer: Environment-specific integration tests
Best Practices
Layer Design Guidelines
- Keep interfaces between layers minimal and focused
- Define clear responsibilities for each layer
- Use factories to create layer components
- Document interfaces between layers thoroughly
- Validate data as it crosses layer boundaries
Layer Implementation Patterns
- Use dependency injection for layer dependencies
- Implement capability detection for optional features
- Handle errors at appropriate layer boundaries
- Log operations at layer transitions when needed
- Use composition to enhance layer functionality
Related Concepts
Related Implementation
- Technical Layer Boundaries Implementation - Technical implementation details and interfaces