Extractable Components: The Fractal Test

Module 15: Fractal Architecture | Expansion Guide

Back to Module 15

The Problem

You built a user authentication module. It works great. Three months later, you start a new project and want to reuse it. You copy the file... and it breaks. It references config.APP_SECRET. It imports from database.models. It calls logger.log_auth_event(). What you thought was a reusable component is tightly coupled to your specific project.

If you can't extract it cleanly, it's not fractal.

The challenge: building components that are self-contained, portable, and work anywhere with minimal adaptation.

The Core Insight

The fractal test: Copy a component to a new file. If it works with just dependency injection changes, it passes. If it needs deep rewrites, it fails.

Fractal components are like Lego bricks: they have standard interfaces and work anywhere. Non-fractal components are like jigsaw pieces: they only fit in one specific spot.

When AI generates code for fractal components, it's portable. AI learns patterns that transfer across projects, not one-off hacks.

The Walkthrough

Non-Fractal Component (Fails Test)

# auth.py - tightly coupled
import config
from database.models import User
from utils.logger import log_auth_event

def authenticate_user(username, password):
    # References global config
    secret = config.APP_SECRET

    # Tightly coupled to specific database model
    user = User.query.filter_by(username=username).first()

    if not user:
        # Tightly coupled to specific logger
        log_auth_event("failed_login", username)
        return None

    # Uses hardcoded hashing (can't swap algorithm)
    if bcrypt.verify(password, user.password_hash):
        log_auth_event("successful_login", username)
        return generate_jwt(user, secret)

    return None

Try to extract this to a new project:

Fractal Test Result: FAIL. Requires deep integration with host project.

Fractal Component (Passes Test)

# auth.py - fractal design
from typing import Protocol, Optional
from dataclasses import dataclass

# Define interfaces, not implementations
class UserRepository(Protocol):
    def find_by_username(self, username: str) -> Optional['UserData']:
        ...

class Logger(Protocol):
    def log(self, event: str, data: dict):
        ...

class PasswordHasher(Protocol):
    def verify(self, password: str, hash: str) -> bool:
        ...

class TokenGenerator(Protocol):
    def generate(self, user_data: dict, secret: str) -> str:
        ...

@dataclass
class UserData:
    username: str
    password_hash: str

class AuthService:
    """Portable authentication service."""

    def __init__(
        self,
        user_repo: UserRepository,
        logger: Logger,
        hasher: PasswordHasher,
        token_gen: TokenGenerator,
        secret: str
    ):
        self.user_repo = user_repo
        self.logger = logger
        self.hasher = hasher
        self.token_gen = token_gen
        self.secret = secret

    def authenticate(self, username: str, password: str) -> Optional[str]:
        """Authenticate user and return token."""
        user = self.user_repo.find_by_username(username)

        if not user:
            self.logger.log("failed_login", {"username": username})
            return None

        if self.hasher.verify(password, user.password_hash):
            self.logger.log("successful_login", {"username": username})
            return self.token_gen.generate(
                {"username": username},
                self.secret
            )

        return None

Extract to new project:

  1. Copy auth.py
  2. Implement the 4 protocols with your project's infrastructure
  3. Inject dependencies
  4. Done!

Fractal Test Result: PASS. Component is self-contained, dependencies are explicit, implementation is swappable.

Why This Matters for AI

When AI sees the fractal version, it understands: "This is a service that takes dependencies." It can suggest similar patterns elsewhere. The non-fractal version looks like project-specific code that can't be generalized.

The Fractal Test Checklist

For any component, ask:

Question Fractal Answer Non-Fractal Answer
Can I copy this file to a new project? Yes, with DI changes only No, needs deep rewrites
Are dependencies explicit? All in constructor/params Hidden imports, globals
Can I swap implementations? Yes, via interfaces No, hardcoded
Does it reference globals? No globals Uses config.*, app.*, etc
Can I test it in isolation? Yes, with mocks Needs full app setup

Test It: The 5-Minute Extract

Literally try to extract your component:

# Create a new empty project
mkdir /tmp/test-extract
cd /tmp/test-extract

# Copy just the component file
cp /path/to/your/component.py .

# Try to use it
# How long does it take to make it work?
# How many files do you need to bring with it?
# How much code do you need to rewrite?

If you can make it work in 5 minutes with just dependency injection, it's fractal.

Patterns for Fractal Components

Pattern 1: Dependency Injection Everywhere

# Non-fractal: Hidden dependencies
class EmailService:
    def send(self, to, subject, body):
        # Where does smtp_config come from?
        smtp = smtplib.SMTP(smtp_config.host, smtp_config.port)
        smtp.send_message(...)

# Fractal: Explicit dependencies
class EmailService:
    def __init__(self, smtp_client: SMTPClient):
        self.smtp = smtp_client

    def send(self, to, subject, body):
        self.smtp.send_message(...)

Pattern 2: Interface Segregation

# Non-fractal: Depends on entire database
class OrderService:
    def __init__(self, database: Database):
        self.db = database

    def create_order(self, order_data):
        self.db.orders.insert(order_data)
        # Now coupled to full database API

# Fractal: Depends on minimal interface
class OrderRepository(Protocol):
    def create(self, order: Order) -> Order:
        ...

class OrderService:
    def __init__(self, orders: OrderRepository):
        self.orders = orders

    def create_order(self, order_data):
        return self.orders.create(Order(**order_data))

Pattern 3: Configuration as Dependency

# Non-fractal: Reads config directly
import os

class CacheService:
    def __init__(self):
        # Coupled to environment variables
        self.ttl = int(os.getenv('CACHE_TTL', '3600'))
        self.redis_url = os.getenv('REDIS_URL')

# Fractal: Config injected
@dataclass
class CacheConfig:
    ttl: int
    redis_url: str

class CacheService:
    def __init__(self, config: CacheConfig):
        self.ttl = config.ttl
        self.redis_url = config.redis_url

Pattern 4: Pure Functions Where Possible

# Non-fractal: Stateful, side-effectful
class PriceCalculator:
    def __init__(self):
        self.tax_rate = load_tax_rate_from_database()

    def calculate_total(self, items):
        # Mixes calculation with data access
        subtotal = sum(item.price for item in items)
        return subtotal * (1 + self.tax_rate)

# Fractal: Pure function, easy to extract
def calculate_total(items: List[Item], tax_rate: float) -> float:
    """Pure function - no side effects, no hidden state."""
    subtotal = sum(item.price for item in items)
    return subtotal * (1 + tax_rate)

Failure Patterns

1. The Singleton Trap

Symptom: Component uses singleton instances, can't be extracted.

# Non-fractal: Singleton dependency
class UserService:
    def get_user(self, user_id):
        # Coupled to global singleton
        return Database.instance().get_user(user_id)

# Fractal: Injected dependency
class UserService:
    def __init__(self, db: Database):
        self.db = db

    def get_user(self, user_id):
        return self.db.get_user(user_id)

2. The Circular Dependency

Symptom: Can't extract A without B, can't extract B without A.

Fix: Introduce interfaces to break cycles.

3. The God Object

Symptom: Component does too much, has 20 dependencies.

Fix: Split into smaller, single-purpose components.

4. The Hidden State

Symptom: Component behavior depends on module-level state.

# Non-fractal: Module state
_cache = {}

def get_user(user_id):
    if user_id in _cache:
        return _cache[user_id]
    # ...

# Fractal: Explicit state
class UserCache:
    def __init__(self):
        self._cache = {}

    def get_user(self, user_id):
        if user_id in self._cache:
            return self._cache[user_id]
        # ...

When Extraction Doesn't Matter

Some code is genuinely project-specific and doesn't need to be fractal:

Focus fractal design on reusable business logic and utilities.

AI Assistance with Fractal Components

Prompt: Make This Fractal

Prompt to AI:
"Refactor this component to be fractal - extractable to a new project with just DI changes.

Current code:
[paste code]

Requirements:
1. All dependencies explicit via constructor
2. Use protocols/interfaces for dependencies
3. No globals or hidden imports
4. Configuration injected, not read directly
5. Should be testable in isolation"

AI will apply dependency inversion, extract interfaces, and make dependencies explicit.

Prompt: Test Extractability

Prompt to AI:
"Analyze this component. Could I copy this file to a new project and use it with minimal changes? List what would need to be adapted.

Code:
[paste code]"

AI will identify coupling points and suggest fixes.

Quick Reference

The Fractal Test:

  1. Copy component to empty project
  2. Implement required interfaces
  3. Inject dependencies
  4. Does it work? If yes → fractal. If no → coupled.

Fractal Component Checklist:

Refactoring to Fractal:

# 1. Find hidden dependencies
grep -r "import config\|os.getenv\|global " component.py

# 2. Extract interfaces
# Create Protocol classes for all external dependencies

# 3. Add DI constructor
# Move all dependencies to __init__

# 4. Test extraction
# Try copying to new project

# 5. Verify
# Can it work with just interface implementations?

Quick Conversion Template:

# Before: Coupled
class MyService:
    def do_thing(self):
        result = global_db.query(...)
        send_email(result)

# After: Fractal
class MyService:
    def __init__(self, db: Database, email: EmailService):
        self.db = db
        self.email = email

    def do_thing(self):
        result = self.db.query(...)
        self.email.send(result)