Put a translation boundary between your domain model and an external or legacy system, so its model never leaks into yours.
Calling a legacy system's API directly from deep inside your domain logic — and passing its field names, quirks, and status codes straight through — means your domain model is now shaped by decisions someone else made in a system you don't control. When that legacy system's model changes or gets replaced, the damage spreads throughout your codebase.
The anti-corruption layer sits between the two systems. It speaks the legacy system's model on one side and your domain's model on the other, translating between them. Your domain code only ever sees its own clean model — it has no idea the legacy system's data shape even exists.
Domain code only ever sees a `Customer`. All of the legacy system's odd field names and status codes stay contained inside the ACL.
// Legacy system's actual shape — verbose, inconsistent field names, numeric status codes.
const legacySystem = {
getCustRecord: (id) => ({
CUST_ID: id, FNAME: "Alex", LNAME: "Rivera", STAT_CD: 2, // 2 = active
}),
};
// Your clean domain model.
class Customer {
constructor({ id, fullName, isActive }) {
this.id = id;
this.fullName = fullName;
this.isActive = isActive;
}
}
// The anti-corruption layer: the only code that knows the legacy shape exists.
class LegacyCustomerAdapter {
getCustomer(id) {
const raw = legacySystem.getCustRecord(id);
return new Customer({
id: raw.CUST_ID,
fullName: `${raw.FNAME} ${raw.LNAME}`,
isActive: raw.STAT_CD === 2,
});
}
}
const acl = new LegacyCustomerAdapter();
const customer = acl.getCustomer("c-1");
console.log(customer); // clean Customer — no CUST_ID, FNAME, or STAT_CD in sight