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.

Medium Python 3.9+ Aug 9, 2026 Microservices patterns 15 views 0 copies

Python code

35 lines
Python 3.9+
class 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

stdout
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

  1. Use a dataclass for the domain model and return an instance instead of a dict.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Microservices patterns

Related tutorials and quizzes for this topic.