Skip to content

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:

typescript
// 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:

typescript
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:

typescript
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:

typescript
// 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:

typescript
// 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:

typescript
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:

typescript
// 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:

typescript
// 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:

  1. Adjacent Layer Communication: Layers should only communicate with adjacent layers
  2. Dependency Direction: Dependencies flow downward (upper layers depend on lower layers)
  3. Interface Abstraction: Layers communicate through well-defined interfaces
  4. Encapsulation: Implementation details are encapsulated within layers
  5. 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

Released under the MIT License.