Choosing Adapter or Facade for Legacy-Service Migration Boundaries
Learn how Adapter and Facade solve different integration problems, how dependency direction and ownership shape the choice, and when both patterns—or neither—belong in a legacy-service migration.
Learning outcome
After this lesson, you can distinguish Adapter from Facade by intent rather than class shape. You will be able to choose a migration boundary based on ownership, dependency direction, and the needs of callers; explain how the patterns can coexist; and recognize when a simple function is sufficient.
Intuition
An Adapter makes one interface usable as another. The consumer already has a preferred contract, but a legacy service speaks a different language. The adapter translates operations, data, errors, and sometimes interaction style so the legacy dependency fits the consumer’s model.
A Facade gives callers a simpler entry point to a complicated subsystem. The underlying services may already be usable, but callers should not need to understand their ordering rules, coordination, or internal structure.
A useful question is: Whose problem is the boundary solving?
- If it protects a consumer from an incompatible dependency, it is probably an Adapter.
- If it protects callers from subsystem complexity, it is probably a Facade.
- If translation and orchestration are both required, the patterns can coexist at different boundaries.
Deep dive
| Dimension | Adapter | Facade |
|---|---|---|
| Primary intent | Convert one contract into another | Simplify access to a subsystem |
| Starting point | A consumer contract and an incompatible provider | Several operations or components that are cumbersome to use directly |
| Typical ownership | Owned near the side that requires the preferred contract | Owned by the team presenting the subsystem capability |
| Dependency direction | Consumer logic depends on its preferred abstraction; the adapter depends on the legacy API | Callers depend on the facade; the facade delegates to subsystem components |
| Translation | Usually central: names, types, errors, units, or protocols | Optional; simplifying access is central, and workflow coordination may sit behind the facade |
| Number of wrapped components | Often one, but not necessarily | Often several, but one complex subsystem can justify a facade |
| Effect on callers | Makes a foreign service look compatible | Makes a complex workflow look simple |
Worked scenario 1: adapting a legacy customer service
A new billing component defines customer eligibility in business terms meaningful to billing. The legacy customer service exposes account status codes, nullable date fields, and transport-specific failures.
Placing those legacy concepts directly in billing would reverse the desired dependency direction: billing policy would become coupled to an external contract. Instead, the billing-owned Adapter implements billing’s preferred customer-information boundary. It translates status codes into billing concepts, normalizes missing values according to an explicit policy, and maps transport failures into errors the billing application can handle.
This is an Adapter because compatibility is the main concern. It should not quietly coordinate invoicing, payment, and notifications; that would add unrelated workflow responsibility.
Worked scenario 2: simplifying account closure
Closing an account requires verifying outstanding balances, revoking credentials, scheduling data retention, recording an audit event, and notifying the customer. Each participating component has an acceptable interface, so incompatibility is not the main problem. The problem is that every caller would otherwise need to know the sequence and failure policy.
An account-closure Facade presents one application-level capability. In this example, its ClosureWorkflow owns the balance-check, revocation, retention, audit, and notification steps and their failure policy. Callers do not depend on the subsystem’s internal topology. Individual legacy services behind the facade may still require Adapters. For example, an audit Adapter can translate the facade’s audit request into the old audit service’s contract.
The Facade therefore simplifies the workflow, while nested Adapters handle incompatible integrations. Their responsibilities remain distinct even when they appear in the same request path.
When a simple function is enough
Do not introduce a named pattern merely because a call needs minor reshaping. A small, stateless conversion used in one local context may be clearer as a function. Escalate to an Adapter when there is a meaningful contract boundary, substitution is useful, or translation policy deserves isolated ownership. Use a Facade when callers would otherwise duplicate coordination or become coupled to subsystem structure.
Failure modes
- Calling every wrapper a Facade: A one-to-one contract translator is still an Adapter, even if its interface is smaller.
- Leaking legacy vocabulary: If consumer code handles old status codes or transport exceptions, the Adapter has not completed the boundary.
- Creating a god Facade: Combining unrelated workflows produces a broad service with weak cohesion and difficult ownership.
- Hiding business policy as translation: An Adapter should translate at the boundary, not become an unreviewed home for pricing or eligibility rules.
- Bypassing required workflow rules: In this account-closure design, a caller must not skip the required balance check. Judge direct subsystem access against that rule: an approved diagnostic read is different from revoking credentials before the required check. Merely routing a call through a wrapper does not prove the rule was enforced.
- Abstracting too early: A named interface and wrapper add navigation cost when a local function would express the entire transformation.
Interview drill
Decision exercises
- A new order service expects money in minor units, while a legacy pricing endpoint returns decimal amounts and currency codes. Which pattern fits?
Answer: Use an Adapter owned near the order service’s integration boundary. The essential task is translating an incompatible provider contract into the consumer’s chosen representation, including a currency-specific scale and an explicit policy for excess precision. For a fictional two-decimal currency, 12.34 becomes 1,234 minor units; 12.345 must be rejected or rounded by an agreed policy, not silently truncated.
- Three valid services must be called in order to provision a tenant, and several API endpoints need that workflow. Which pattern fits?
Answer: Expose tenant provisioning through a Facade. In this example, its ProvisionTenant workflow owns the three-step order and the response to each partial failure. Add Adapters only where an individual service contract is incompatible.
- One endpoint needs to trim and normalize a label before passing it to an otherwise compatible service. Which pattern fits?
Answer: Start with a simple function. There is no substantial integration contract or subsystem complexity. Promote it only if normalization becomes shared policy or the external boundary develops meaningful translation concerns.
Five interview questions
- What difference in intent separates Adapter from Facade even when both wrap another object?
- How does consumer ownership of an interface help keep dependencies pointed away from a legacy service?
- When can an Adapter legitimately sit behind a Facade?
- What evidence suggests that a Facade has become an incoherent god service?
- How would you decide whether a transformation deserves an Adapter rather than a local function?
Answer checkpoints
- Intent: The customer-service Adapter satisfies billing's preferred contract. The account-closure Facade reduces what callers must know about the closure subsystem. Counting wrapped methods is not the deciding test.
- Dependency direction: Billing refers to its own customer-information contract and domain concepts. The legacy implementation refers to that contract and the legacy API. Ownership still needs enforcement: importing old status constants into billing would reintroduce the coupling.
- Both together: The closure workflow can ask an audit Adapter to translate its audit request. The workflow decides when auditing is required; the Adapter implements the old audit protocol.
- Too broad: Account closure, pricing experiments, marketing reports, and inventory corrections accumulating in one class would have unrelated reasons to change and different owners. Split by coherent capability, not by an arbitrary class-size threshold.
- Function or Adapter: Keep the one-endpoint label normalization as a function. The customer boundary earns an Adapter when its legacy status, missing-date, and failure mappings need a separately tested contract.
Make one migration mapping testable
For this exercise only, suppose the old service defines status A as active, S as suspended, and a missing review date as unknown. For a successful lookup, the new boundary returns Active, Suspended, or Unknown plus an optional review date. A transport failure is a separate Unavailable outcome. Billing requires both an active account and a known review date before invoicing.
- Input
Aplus a valid date: Adapter returnsActiveand the date; billing may continue if its other rules pass. - Input
Aplus no date: Adapter preserves the missing date; billing defers the invoice under the stated rule. The Adapter must not invent today's date. - Input
S: Adapter returnsSuspended; billing does not invoice. - Input an undocumented status: return
Unknown; billing does not invoice. - A transport failure: return
Unavailable; defer for recovery rather than treating a failed lookup as an active account.
Run the same boundary cases against both old and replacement implementations before switching callers. If the replacement cannot express a required outcome, change the contract deliberately rather than silently weakening it. These cases test this fictional mapping and billing rule, not any real legacy product's status codes.
Revision checklist
- Identify whether the real problem is incompatibility or complexity.
- Keep consumer business logic independent of legacy contracts.
- Give orchestration and translation separate, explicit responsibilities.
- Place ownership where the preferred contract or presented capability is governed.
- Permit Adapters behind a Facade when both concerns exist.
- Prefer a simple function when the transformation is local and policy-light.
- Prevent callers from bypassing a boundary whose rules must remain consistent.
TechInterviewNotes Editorial Team · Updated 7 Oct 2026 ·