Structum Anti-Patterns

Purpose: Learn what NOT to do.
Audience: Plugin Developers & Reviewers.

This guide documents common patterns that violate the Architectural Constitution.


A-1. The “Hidden Coupler” (Violates C-2, C-4)

The Plugin imports a specific implementation inside its logic, making it impossible to swap.

❌ (A-1) The Anti-Pattern

# packages/myplugin/src/structum/plugins/myplugin/service.py
from structum.plugins.database.sqlalchemy import SQLAlchemyDatabase  # HARD COUPLING!

class MyService:
    def __init__(self):
        # Implicitly assumes SQLAlchemy backend
        self.db = SQLAlchemyDatabase() 

✅ (A-1) The Structum Way

# packages/myplugin/src/structum/plugins/myplugin/service.py
from structum.database.interfaces import DatabaseInterface  # Protocol only

class MyService:
    def __init__(self, db: DatabaseInterface):
        self.db = db  # Injected, agnostic

A-2. The “Side-Effect Import” (Violates C-2)

The module executes logic just by being imported.

❌ (A-2) The Anti-Pattern

# packages/myplugin/src/structum/plugins/myplugin/__init__.py
import logging

# 😱 RUNS ON IMPORT!
logging.basicConfig(level=logging.INFO)
print("MyPlugin Initialized!") 

✅ (A-2) The Structum Way

# packages/myplugin/src/structum/plugins/myplugin/__init__.py
def setup_logging():
    """Must be called explicitly."""
    ...

A-3. The “Zombie Config” (Violates C-6)

The plugin allows the app to start with invalid config, crashing only when the feature is used hours later.

❌ (A-3) The Anti-Pattern

class PaymentProcessor:
    def process(self):
        # 😱 Checks config at Runtime!
        key = get_config().get("STRIPE_KEY") 
        if not key:
            raise RuntimeError("Missing API Key")

✅ (A-3) The Structum Way

# Validated at Bootstrap time
boot.add_validator(EnvValidator(required=["STRIPE_KEY"]))

class PaymentProcessor:
    def __init__(self, api_key: str):
        # Guaranteed to exist
        self.key = api_key

A-4. The “God Context” (Violates C-3)

Passing a massive, framework-tied object deeper into the domain than necessary.

❌ (A-4) The Anti-Pattern

# Leaking FastAPI 'Request' object into business logic
def calculate_tax(request: Request, amount: Decimal) -> Decimal:
    user = request.state.user  # Business logic depends on HTTP framework!
    ...

✅ (A-4) The Structum Way

# Extract current user at the boundary
def calculate_tax(user: User, amount: Decimal) -> Decimal:
    ...

A-5. The “Swallowed Error” (Violates C-5)

Catching exceptions without handling them, leaving the system in an unknown state.

❌ (A-5) The Anti-Pattern

try:
    db.commit()
except Exception:
    # 😱 System continues as if data was saved!
    log.error("Oops") 

✅ (A-5) The Structum Way

try:
    db.commit()
except Exception as e:
    # Explicit failure
    raise PersistenceError("Failed to save order") from e