/polyArch v2 - Self-Configuring Polymorphic Architecture

Markdown Code Visible HERE

Core Rules Summary (TL;DR for LLMs)


Overview

The Self-Configuring Polymorphic Architecture pattern (v2) creates robust, enterprise-grade applications by composing self-configuring components that present formal, uniform interfaces. This version refines the original pattern with clearer guidelines for dependency management, scalability, and safety.

Fundamental Principle: Complete Module Locality

To the maximum extent possible, all information, code, config, and resources needed to understand, debug, and extend a module should be present in that module.

This remains the cornerstone of the architecture. Its goal is to enable a developer to open a single file and gain a complete understanding of that module's behavior and dependencies.

Naming Principle: Descriptive and Searchable

Never use common words (value, data, library, etc.) for names except for minimal local variables used within a few lines.

This principle is critical for maintaining clarity in a system composed of many small modules. Names must be descriptive, uniquely searchable, and specific.

Core Principles

1. Self-Configuration via a Global Context (The Exception)

While global state is generally an anti-pattern, a pragmatic exception can be made for a small, immutable set of truly universal utilities. Functions for logging and configuration access are candidates for this approach.

By placing these on process.global, they become available throughout the codebase without the need for repetitive dependency injection.

Crucial Safety Guideline: If using this pattern, the global object must be frozen at application startup to prevent accidental modification.

// main.js - At startup
const { xLog, getConfig, commandLineParameters } = require('./lib/setup-env');

// Define the global context
process.global = { xLog, getConfig, commandLineParameters };

// Freeze it to prevent any modification
Object.freeze(process.global);

// Now any module can safely use it
const { xLog, getConfig } = process.global;

2. Self-Containment and Injected Shared Resources

Modules should instantiate their own stateless dependencies locally. However, any dependency that is stateful, expensive, or manages a shared resource pool must be injected by the orchestrator. This is the key distinction between a shared resource (like a DB connection) and a global utility (like a logger).

3. Formal, Polymorphic Interfaces

Components with varying implementations must adhere to a formally defined interface. This provides a strong contract for developers and allows the orchestrator to use components interchangeably.

In TypeScript, this should be enforced with interface:

// /interfaces/DataExtractor.ts
export interface DataExtractor {
    extract(source: string): Array<any>;
    getSourceType(): string;
    describe(): string;
}

In JavaScript, use JSDoc for static analysis. This provides type-checking hints to editors and documents the expected contract.

/**
 * @interface DataExtractor
 * @property {function(string): Array<Object>} extract - Extracts data from a source string.
 * @property {function(): string} getSourceType - Returns the type of the source (e.g., 'json', 'csv').
 * @property {function(): string} describe - Provides a human-readable description of the extractor.
 */

4. Extensible Factories via The Registry Pattern

To avoid violating the Open/Closed Principle, use a Registry Pattern instead of a switch statement to select a component's implementation. This allows new implementations to be added without modifying the factory logic.

A typical directory structure for this pattern would be:

/fact-extractor
├── index.js         # The factory/registry
└── /extractors
    ├── json-extractor.js
    └── csv-extractor.js

The factory (index.js) dynamically selects the correct implementation:

// /fact-extractor/index.js - The Factory using a Registry
const implementations = {
    json: require('./extractors/json-extractor'),
    csv: require('./extractors/csv-extractor')
    // To extend, add a new file and a new line here. No logic change needed.
};

const moduleFunction = (injectedDependencies) => {
    const { getConfig } = process.global;
    const localConfig = getConfig('fact-extractor');
    const extractionMode = localConfig.defaultExtractionMode;

    if (!implementations[extractionMode]) {
        throw new Error(`Unknown extraction mode: ${extractionMode}`);
    }

    // Return the configured implementation, passing through any injected resources
    return implementations[extractionMode](injectedDependencies);
};

module.exports = moduleFunction;

Architectural Guideline: Resource Management

A dependency must be externalized and injected if it manages:

Example: Database Connection vs. Logger

// ✅ CORRECT: The orchestrator manages the shared resource.
// main.js
const db = require('better-sqlite3')(process.env.DB_PATH); // Create once
const reporter = require('./reporter')({ db }); // Inject resource

// reporter.js
const moduleFunction = ({ db }) => { // Receive shared DB handle
    const { getConfig, xLog } = process.global; // Use global utilities
    const localConfig = getConfig('reporter');

    const generateReport = (params) => {
        xLog.info(`Generating report with query: ${localConfig.query}`);
        const data = db.prepare(localConfig.query).all();
        // ...
    };
    return { generateReport };
};

Anti-Patterns to Avoid

Summary

The Self-Configuring Polymorphic Architecture (v2) provides a robust framework for building clean, maintainable, and scalable applications by enforcing:

  1. Complete Module Locality: Everything needed to understand a module is in one place.
  2. A Frozen Global Context: A pragmatic exception for universal, immutable utilities like logging and configuration access.
  3. Strict Injection of Shared Resources: Stateful or expensive dependencies are managed by the orchestrator and explicitly injected.
  4. Formal Polymorphic Interfaces: Enforced contracts prevent architectural drift.
  5. Extensible Factories: The Registry Pattern allows for safe and easy extension.

The key insight is refined: A developer should be able to open a single file and understand everything about how that module works, knowing that its only hidden dependencies are a small, predictable set of frozen global utilities.