How to Migrate a Legacy Facade with the Strangler Fig Pattern in Python
Use a facade to wrap a legacy API and incrementally migrate callers to a modern interface, following the strangler fig pattern.
Python code
31 linesclass LegacyAPI:
"""Simulates the legacy system's raw interface."""
def get_user(self, user_id):
return {"id": user_id, "name": "Alice", "legacy": True}
class UserService:
"""Facade that wraps the legacy system with a modern interface."""
def __init__(self, legacy_api=None):
self.legacy = legacy_api or LegacyAPI()
def fetch_user(self, user_id):
data = self.legacy.get_user(user_id)
return {"id": data["id"], "display_name": data["name"], "is_legacy": data["legacy"]}
def main():
legacy = LegacyAPI()
service = UserService(legacy)
legacy_result = legacy.get_user(42)
facade_result = service.fetch_user(42)
print("Legacy raw:", legacy_result)
print("Facade mapped:", facade_result)
assert facade_result["display_name"] == "Alice"
assert facade_result["is_legacy"] is True
if __name__ == "__main__":
main()
Output
Legacy raw: {'id': 42, 'name': 'Alice', 'legacy': True}
Facade mapped: {'id': 42, 'display_name': 'Alice', 'is_legacy': True}
How it works
The facade (UserService) isolates callers from the legacy system's raw structure. When you change the underlying API, you only update the facade, not every consumer. This is the strangler fig pattern in practice: the new interface gradually replaces legacy calls without breaking existing code. The legacy_api parameter with a default also makes the facade easy to mock in tests, so you can prototype the new system without touching production.
Common mistakes
- Exposing legacy field names (e.g., `name`) in the facade instead of mapping to a stable domain model
- Forgetting to mock the legacy API in tests, so tests depend on a live legacy system
- Making the facade stateless and caching nothing, causing repeated legacy calls in high-traffic paths
Variations
- Use dependency injection to swap the facade implementation at runtime for A/B testing or feature flags
- Add a versioned method like `fetch_user_v2` to support non-breaking API evolution as the strangler fig grows
Real-world use cases
- Gradually porting a monolithic CRM to microservices while keeping old API consumers functional.
- Wrapping a third-party payment gateway inside a facade to switch providers without touching business logic.
- Introducing a new data source (e.g., a read replica) behind a facade to minimize downtime during migration.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.