Wrapper Pattern for Dual-Interface Modules
Extracted from CloudOps-Runbooks VPC module. Describes a reusable architecture pattern for any module that needs both CLI and programmatic interfaces.
Problem
Many operational modules need two interfaces:
- CLI — for technical users running commands in terminals
- Programmatic (Jupyter/scripts) — for non-technical users or automation pipelines
Without a pattern, this leads to:
- Business logic duplicated between CLI handlers and notebook cells
- Inconsistent outputs (CLI shows one format, notebook shows another)
- Difficult testing (must test both code paths independently)
Decision: Wrapper Architecture
Centralise business logic in a wrapper class. Both CLI and programmatic interfaces delegate to the same wrapper.
Structure
src/module/
├── __init__.py # Module exports
├── wrapper.py # Business logic (single source of truth)
├── cost_engine.py # Domain-specific calculations
├── heatmap_engine.py # Visualisation logic
└── rich_formatters.py # Output formatting utilities
How It Works
# Wrapper holds all business logic
wrapper = ModuleWrapper(profile="aws-profile", console=console)
# CLI calls the wrapper
# (Click/Typer handler is thin — just arg parsing + wrapper call)
results = wrapper.analyze()
# Notebook calls the same wrapper
# (Cell is thin — just wrapper call + display)
results = wrapper.analyze()
Before (Anti-Pattern)
# Notebook cell: 200+ lines of duplicated business logic
class NetworkingCostHeatMapEngine:
def __init__(self, config):
# ... complex initialisation
def _generate_time_series(self):
# ... 50 lines of calculation
# ... more duplicated methods
After (Wrapper Pattern)
# Notebook cell: 3 lines — just the customer journey
from module import ModuleWrapper
wrapper = ModuleWrapper(profile=PROFILE)
results = wrapper.analyze()
# Rich display happens automatically
Design Principles
DRY
Business logic exists in exactly one place (the wrapper). CLI and notebooks are thin adapters.
Single Responsibility
- Wrapper: orchestrates business operations
- Engine classes: domain-specific calculations
- Formatters: output presentation
- CLI handler: argument parsing only
- Notebook: user journey only
Testability
Wrapper classes can be unit-tested independently of CLI framework or notebook runtime.
When to Use This Pattern
| Scenario | Use Wrapper? |
|---|---|
| Module has CLI + notebook users | Yes |
| Module has CLI only | No — keep it simple |
| Module has notebook only | No — keep it simple |
| Module may gain a second interface later | No — apply YAGNI; add wrapper when needed |
Applicability to ADLC
This pattern applies to any consumer project module that serves multiple interfaces:
- CloudOps runbook modules (CLI + Jupyter)
- FinOps analysis tools (CLI + dashboard)
- Monitoring utilities (CLI + API)
The key insight: the wrapper is the business logic boundary. Everything above it (CLI, notebook, API) is an adapter. Everything below it (engines, AWS clients) is an implementation detail.
Origin: CloudOps-Runbooks VPC networking module. Generalised for ADLC framework guidance.