When a service integrates with a legacy system or a vendor API whose model does not match its own, letting that foreign model spread through the code couples the service to someone else’s design. An anti-corruption layer translates at the boundary, so the service’s domain stays clean and independent.
Before you start
You should be comfortable with domain modeling and service boundaries. This article covers the translation boundary; it assumes basic data-mapping skills.
Step-by-step walkthrough
Step 1: Define your own model first
Start with the model your service needs, named in your domain’s language, rather than importing the vendor’s types. This keeps the core logic independent and testable, and it makes the foreign dependency an implementation detail behind the boundary.
Step 2: Translate at a single boundary
Write one adapter that maps the foreign payload to your model and back, with its own DTOs. Every call to the external system passes through it, so a change on their side touches one file instead of the whole codebase.
Step 3: Keep foreign types from leaking
Do not expose the foreign types in your public interfaces or persistence. If a vendor object appears in your handlers or database, the isolation is gone and the coupling returns. The adapter returns your types only.
Worked scenario
The adapter maps a vendor payload to the domain type.
interface VendorOrder { order_id: string; total_cents: number }
interface Order { id: string; total: number }
function toOrder(vendor: VendorOrder): Order {
return { id: vendor.order_id, total: vendor.total_cents / 100 };
}Walk through the example
toOrder converts the vendor’s naming and units to the domain’s. If the vendor renames order_id or changes units, only this function changes, and the rest of the service is untouched. The domain type Order never depends on VendorOrder, which is the isolation the layer provides.
Common mistake
Sharing DTOs across services or letting the vendor’s model become the internal model, which spreads the foreign coupling everywhere. Another is doing the translation in many places, so behavior drifts and fixes must be repeated.
Verify the behavior
Change a field name in the vendor payload and confirm only the adapter fails, not the domain. Assert the domain tests run without any vendor fixture. Check that no vendor type appears in the service’s public interfaces or database schema.
Interview exercise
Where should the anti-corruption layer live: in the integrating service or the legacy system?
Answer and reasoning
In the consuming service, because that is the side that needs its own model protected. Putting it in the legacy system would require changing the system you are trying to isolate from, which is usually the harder and riskier side. The consumer owns the translation and can evolve it as its own model changes.
Continue learning
Compare boundaries in Service boundaries and Data ownership. Read the microservices.io patterns and try the Microservices interview questions.