Layer Boundaries
This document defines the clear boundaries between the architectural layers in the ReX system, establishing responsibilities, interfaces, and interaction patterns for each layer.
System Architecture Overview
The ReX system is organized into the following layers, from highest to lowest level:
Each layer has distinct responsibilities and communicates with adjacent layers through well-defined interfaces. This separation ensures:
- Maintainability: Changes in one layer don’t ripple through the entire system
- Testability: Each layer can be tested in isolation with appropriate mocks
- Extensibility: New implementations can be added at each layer without affecting others
- Adaptability: The system can adapt to different environments by swapping layer implementations
Application Layer
The Application Layer provides user-facing abstractions and integration with UI frameworks.
Responsibilities
- Integrate with React through hooks and components
- Provide declarative access to content operations
- Handle UI-specific concerns like loading states and error presentation
- Manage component lifecycle in relation to content subscriptions
- Present content in appropriate formats for display
Key Components
React Hooks
function useContent(uri: string): {
content: Content | null
loading: boolean
error: Error | null
refresh: () => Promise<void>
}
function useContentWrite(): {
write: (uri: string, content: Content) => Promise<void>
writing: boolean
error: Error | null
}
function useContentList(query?: ContentQuery): {
uris: string[]
loading: boolean
error: Error | null
}
React Components
interface ContentDisplayProps {
uri: string
fallback?: React.ReactNode
errorComponent?: React.ComponentType<{ error: Error }>
transformContent?: (content: Content) => Content
}
interface ContentEditorProps {
uri: string
initialContent?: Content
onSave?: (content: Content) => void
autoSave?: boolean
saveInterval?: number
}
Interface With Store Layer
- Consumes: ContentStore instance and operations
- Provides: React integration for content access and manipulation
- Pattern: Hooks wrap store operations with React lifecycle management
Implementation Patterns
- Subscription Management: Hooks subscribe to content changes and clean up on unmount
- Error Boundaries: Components use React error boundaries for graceful failure
- Progressive Enhancement: Components adapt to available capabilities
- Separation of Concerns: Data access through hooks, presentation through components
Store Layer
The Store Layer provides high-level content operations and serves as the primary API for application code.
Responsibilities
- Expose unified content operations (read, write, delete, list)
- Coordinate middleware pipeline for operations
- Provide content change notification system
- Support content Uri resolution and normalization
- Apply content transformations as needed
Key Components
Content Store
interface ContentStore {
// Core operations
read<T = string>(uri: string): Promise<Content<T>>
write<T = string>(uri: string, content: Content<T>): Promise<void>
delete(uri: string): Promise<void>
list(query?: ContentQuery): Promise<string[]>
// Change notification
onChange(listener: ContentChangeListener): UnsubscribeFunction
// Resource management
dispose(): Promise<void>
}
type ContentChangeListener = (
uri: string,
content: Content | null,
changeType: ChangeType
) => void
type ChangeType = 'created' | 'updated' | 'deleted' | 'moved'
Content Registry
interface ContentRegistry {
// Store management
getStore(id?: string): ContentStore
createStore(options?: StoreOptions): ContentStore
registerStore(id: string, store: ContentStore): void
// Global operations
getContent<T = string>(uri: string): Promise<Content<T>>
listContent(query?: ContentQuery): Promise<string[]>
// Resource management
dispose(): Promise<void>
}
Interface With Adjacent Layers
- Consumes: Middleware-enhanced adapter operations
- Provides: High-level content operations to applications
- Pattern: Factory functions create stores with appropriate middleware
Implementation Patterns
- Factory Functions:
createContentStore()
configures store with appropriate adapters and middleware - Registry Pattern: ContentRegistry manages multiple stores for complex applications
- Observer Pattern: Change notification system allows reactive updates
- Configuration Objects: Options pattern for flexible store creation
Middleware Layer
The Middleware Layer provides cross-cutting concerns and function enhancement.
Responsibilities
- Implement cross-cutting concerns like caching, validation, logging
- Transform content between operations
- Enhance adapter behavior without modifying adapters
- Provide composition utilities for building middleware chains
- Implement operation hooks for before/after processing
Key Components
Middleware Definition
type Middleware<T = any> = (
next: (uri: string, options?: OperationOptions) => Promise<T>
) => (uri: string, options?: OperationOptions) => Promise<T>
interface OperationOptions {
signal?: AbortSignal
[key: string]: unknown
}
Middleware Factories
function withCaching(options?: CacheOptions): Middleware
function withValidation(schema: ValidationSchema): Middleware
function withLogging(options?: LogOptions): Middleware
function withErrorHandling(handlers: ErrorHandlers): Middleware
Composition Utilities
function pipe<T>(...fns: Array<(arg: T) => T>): (arg: T) => T
function compose<T>(...fns: Array<(arg: T) => T>): (arg: T) => T
Interface With Adjacent Layers
- Consumes: Adapter operations
- Provides: Enhanced operations to the Store Layer
- Pattern: Function composition using higher-order functions
Implementation Patterns
- Higher-Order Functions: Middleware are functions that return functions
- Function Composition: pipe() and compose() utilities for combining middleware
- Options Objects: Configuration through factory function parameters
- Context Passing: OperationOptions for sharing context between middleware
Adapter Layer
The Adapter Layer provides an abstraction over storage mechanisms with a consistent interface.
Responsibilities
- Abstract storage details behind a consistent interface
- Handle environment-specific storage implementations
- Provide capability-based feature detection
- Implement serialization for storage-specific formats
- Handle storage-specific error mapping
Key Components
Adapter Interface
interface ContentAdapter<T = string> {
// Core operations
read(uri: string, options?: OperationOptions): Promise<Content<T>>
write(
uri: string,
content: Content<T>,
options?: OperationOptions
): Promise<void>
delete(uri: string, options?: OperationOptions): Promise<void>
list(query?: ContentQuery, options?: OperationOptions): Promise<string[]>
// Capability information
capabilities: AdapterCapability[]
// Optional advanced operations
move?(
uri: string,
targetUri: string,
options?: OperationOptions
): Promise<void>
copy?(
uri: string,
targetUri: string,
options?: OperationOptions
): Promise<void>
watch?(uri: string, callback: WatchCallback): UnsubscribeFunction
// Events (optional)
events?: EventEmitter
// Resource management
dispose(): Promise<void>
}
type AdapterCapability =
| 'read'
| 'write'
| 'delete'
| 'list'
| 'move'
| 'copy'
| 'watch'
Adapter Factories
function createMemoryAdapter(options?: MemoryAdapterOptions): ContentAdapter
function createFileSystemAdapter(
options?: FileSystemAdapterOptions
): ContentAdapter
function createIndexedDBAdapter(
options?: IndexedDBAdapterOptions
): ContentAdapter
function createHttpAdapter(options?: HttpAdapterOptions): ContentAdapter
Interface With Adjacent Layers
- Consumes: Storage Layer operations
- Provides: Storage abstraction to Middleware Layer
- Pattern: Factory functions create adapters with appropriate options
Implementation Patterns
- Factory Functions: Adapter creation with consistent patterns
- Capability Declaration: Explicit capability listing for feature detection
- Serialization Handling: Standardized approach to content serialization
- Error Mapping: Storage-specific errors mapped to standard ContentError types
Storage Layer
The Storage Layer handles direct interaction with physical storage mechanisms.
Responsibilities
- Provide direct access to storage mechanisms (filesystem, IndexedDB, etc.)
- Handle environment-specific storage operations
- Implement low-level serialization and deserialization
- Manage storage-specific resource lifecycle
- Handle storage-specific error handling
Key Components
Environment-Specific Storage
- Node.js: File system operations, fs/promises API
- Browser: IndexedDB, localStorage, sessionStorage
- Service Worker: Cache API, IndexedDB
- Remote: Fetch API, XMLHttpRequest
Serialization Utilities
function serializeContent<T>(content: Content<T>): string | Uint8Array
function deserializeContent<T>(
data: string | Uint8Array,
contentType: string
): Content<T>
Error Handling
function mapStorageError(error: unknown, uri: string): ContentError
Interface With Adapter Layer
- Consumes: N/A (lowest layer)
- Provides: Storage operations to Adapter Layer
- Pattern: Environment detection for appropriate implementation selection
Implementation Patterns
- Environment Detection: Utilities like isNode(), isBrowser() for implementation selection
- Capability Testing: Feature detection for available APIs
- Error Mapping: Storage-specific errors mapped to standard error types
- Resource Cleanup: Proper handling of connection closing, file descriptors, etc.
Cross-Layer Considerations
Error Handling
Error handling follows a consistent pattern across layers:
- Storage Layer: Creates native storage errors
- Adapter Layer: Maps storage errors to ContentError types
- Middleware Layer: May handle, transform, or enhance errors
- Store Layer: Provides consistent error handling for content operations
- Application Layer: Presents errors in UI-appropriate formats
class ContentError extends Error {
uri: string
source: string
recoverable: boolean
cause?: unknown
}
class ContentNotFoundError extends ContentError {}
class ContentAccessError extends ContentError {}
class ContentValidationError extends ContentError {}
class ContentFormatError extends ContentError {}
Environment Adaptability
The system adapts to different environments through:
- Environment Detection: Utilities detect environment capabilities
- Conditional Imports: Import environment-specific modules as needed
- Capability Detection: Runtime testing for available features
- Progressive Enhancement: Core functionality with optional enhancements
- Fallback Mechanisms: Graceful degradation when features unavailable
Type Safety
Type safety is maintained through:
- Generic Types: Content<T> for different content data types
- Type Transformations: Explicit transformation functions between types
- Type Guards: Runtime type checking where needed
- Interface Consistency: Consistent interfaces across layers
- Typescript Strictness: Strict mode and thorough type checking
Implementation Strategy
When implementing or modifying components, follow these principles:
- Respect Layer Boundaries: Components should only depend on adjacent layers
- Interface Stability: Public interfaces should be stable and backward compatible
- Implementation Flexibility: Implementations can change as long as interfaces remain stable
- Testing Isolation: Tests should mock adjacent layer dependencies
- Documentation Clarity: Document which layer a component belongs to
Related Architecture Decisions
- ADR-007: Compositional Content Architecture
- ADR-003: Registry Facade Architecture
- ADR-009: Unified Type System
- ADR-010: Plugin Architecture
Related Concepts
- Layer Boundaries Concept - Conceptual explanation of the layer boundaries