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