How to Build an Anti-Corruption Layer in Python
Translate messy legacy system data into a clean domain model using an anti-corruption layer in Python.
Python code
35 linesclass MockLegacySystem:
"""Simulates a legacy system with messy data formats."""
def get_user_data(self):
# Legacy format: fields are abbreviated and types are inconsistent
return {
"usr_id": "USR-123",
"usr_nm": "john_doe",
"email_addrs": "John.Doe@example.com",
"age_str": "35",
"is_actv": "Y",
"prm_lvl": 2
}
class AntiCorruptionLayer:
"""Translates legacy data into a clean domain model."""
PRIVILEGE_MAP = {1: "viewer", 2: "editor", 3: "admin"}
@staticmethod
def translate_legacy_to_domain(legacy_user: dict) -> dict:
return {
"id": int(legacy_user["usr_id"].split("-")[1]),
"username": legacy_user["usr_nm"],
"email": legacy_user["email_addrs"].lower(),
"age": int(legacy_user["age_str"]),
"active": legacy_user["is_actv"] == "Y",
"privilege_level": AntiCorruptionLayer.PRIVILEGE_MAP[
legacy_user["prm_lvl"]
]
}
if __name__ == "__main__":
legacy = MockLegacySystem().get_user_data()
domain_user = AntiCorruptionLayer.translate_legacy_to_domain(legacy)
for key, value in domain_user.items():
print(f"{key}: {value}")
Output
id: 123
username: john_doe
email: john.doe@example.com
age: 35
active: True
privilege_level: editor
How it works
The AntiCorruptionLayer acts as a boundary that converts legacy format fields (abbreviated names, inconsistent types) into a standardized domain model. Each mapping explicitly handles type conversion, like extracting the numeric ID from USR-123 or casting age_str to an integer. The PRIVILEGE_MAP translates numeric codes into human-readable labels, isolating the rest of the codebase from legacy quirks. The static method keeps the translation logic reusable without requiring an instance.
Common mistakes
- Assuming legacy keys always exist — always use `.get()` with defaults for missing fields.
- Forgetting to handle inconsistent types, e.g., age as a string instead of int.
- Hardcoding legacy field names in the domain layer instead of centralizing them in the ACL.
Variations
- Use a dataclass for the domain model and return an instance instead of a dict.
- Add error handling with `try/except` to raise a custom `TranslationError` for unsupported privilege levels.
Real-world use cases
- Integrating a legacy mainframe system into a modern microservices architecture.
- Normalizing data from multiple vendor APIs into a single internal data model.
- Migrating data from an outdated database schema to a new one without rewriting domain services.
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.