You're the architect reviewing a proposal to build a dedicated anti-corruption layer service in front of a newly launched, actively-co-developed internal 'Catalog' service that another team on the same roadmap owns and is happy to evolve jointly with you. The proposing team argues 'ACLs are always good practice.' How would you push back, and what would make you decide an ACL is NOT worth building here?
answer
- ACL is for untrusted/uncontrolled upstream, not everywhere
- co-owned models: use contract testing, not ACL
- translator lag vs legitimate co-evolution = wasted work
- false sense of independence risk
- revisit decision when relationship/control changes
basics
~20 sAn ACL isn't free - it's worth it when you can't influence or trust the upstream model, e.g. legacy, third-party, or unstable systems. If you can jointly evolve the API with a cooperative team, negotiating a shared contract is usually cheaper and better than building a translation layer against a moving target.
solid answer
~60 sI'd push back that an ACL is a cost paid to buy independence from a model you don't control or don't trust, and here you control it jointly - the two teams can just agree on and evolve a shared contract instead. Building a full ACL against Catalog's model while it's still actively co-developed means you're now translating against a moving target, and every legitimate joint change requires updating the translator too, which is pure overhead with no isolation benefit since you could've influenced the model directly. I'd only recommend an ACL here if there's a specific reason to expect divergence later - e.g. Catalog is likely to be replaced, is expected to accumulate model debt the consuming team doesn't want to inherit, or the relationship will become less cooperative, such as Catalog becoming a widely-shared platform service that can no longer accommodate this one consumer's preferences. Absent that, I'd recommend collaborating on a clean shared API/event contract and skip the extra translation layer, revisiting the decision if the relationship or Catalog's stability changes.
go deeper
Not expected to make this call independently; should be able to follow the reasoning that a friendly, cooperating team is different from an untrusted legacy system, if walked through it.
Should recognize that ACLs have a real ongoing cost, and that a cooperating co-owned relationship doesn't need one; may not yet propose concrete alternatives like contract testing.
Should propose a concrete alternative such as collaborative API design or consumer-driven contract testing, and articulate the maintenance cost of an ACL chasing a co-evolving model.
Should frame this as an explicit cost-benefit and organizational-trust judgment call, name the signals that should trigger revisiting the decision later, and recognize both the over-application and under-application failure modes.
## The factors that decide it Deciding whether an ACL is worth building comes down to weighing a small number of concrete factors against each other: 1. how much **influence** you have over the upstream model; 2. how **stable and trustworthy** that model is expected to stay; 3. how much translation **work** the mapping actually requires; 4. how likely the two models are to **diverge** in the future. When a consuming team can sit in the same planning meetings as the producing team, propose changes to the shared model, and expect those changes to land, the 'protection from an untrustworthy foreign model' rationale for an ACL simply doesn't apply — there's no foreign model, there's a jointly-owned one. In that situation, the right tool isn't a translation layer; it's collaborative API design, an API-first or contract-first process, backed by something like consumer-driven contract testing, so both sides agree on a shape and get fast feedback the moment either side's changes would break the other. ## Why 'always good practice' is the wrong frame The reason to push back on 'ACLs are always good practice' is that this treats a targeted, context-dependent pattern as a universal default, which inverts the actual guidance behind it. The anti-corruption layer earns its name specifically because it exists to prevent corruption from a source you cannot control or trust to stay coherent: - a legacy system nobody actively maintains toward your needs; - a third-party vendor whose model changes are unilateral and undocumented; - or an organizational boundary so wide that coordination isn't practical. None of those conditions hold for two teams on the same roadmap actively co-developing a service together; applying the pattern there mistakes 'is an integration point' for 'is an untrustworthy integration point,' and builds costly insulation against a threat that doesn't exist yet. ## What building one anyway costs The cost of building one anyway is concrete and ongoing, not one-time. - While Catalog is actively co-developed, its model will keep changing in ways both teams agreed to and want reflected — every one of those legitimate, wanted changes now has to be **manually re-propagated into the ACL's translator**, which is pure overhead: work that produces zero isolation benefit, because the two models are supposed to track each other closely by design during this phase. - There's also an **opportunity cost** — the engineering time spent maintaining a translator chasing a moving target could instead go toward improving the shared contract itself, or toward the product work the ACL proposal is nominally protecting. - And there's a subtler cost: an ACL creates a **false sense of independence**. The consuming team may start believing changes to Catalog's model are 'safely absorbed' by the ACL and stop paying close attention to Catalog's roadmap, when in reality close coordination is still exactly what's needed while the two services are young and co-evolving. ## Both failure modes The failure mode from **over-applying** ACLs here shows up as duplicated modeling work masquerading as safety: both teams end up maintaining two parallel representations of essentially the same concepts (Catalog's model and the ACL's translated model) that have to be kept in sync by hand, doubling the surface area for a bug where the translator silently falls behind a Catalog change and starts returning stale or wrong values to the consumer — exactly the kind of drift an ACL is supposed to prevent, now caused by the ACL's own maintenance lag instead of by the upstream being untrustworthy. The inverse failure — **not building an ACL** when one was actually warranted — shows up differently: teams keep depending directly on what they assumed was a stable jointly-owned model, and get blindsided later when Catalog is acquired by a different, less cooperative team, opened up to many more consumers with competing needs, or simply outlives its original close working relationship and starts changing in ways this consumer no longer has influence over. The judgment call that actually matters is recognizing when that transition happens and building the ACL retroactively at that point, not reflexively on day one. ## The right call each way A good real-world instance of the right call each way: | The situation | What teams do | |---|---| | Inside a single product organization, two feature teams building complementary services for the same release | typically skip a full ACL and instead use an API-first workflow with consumer-driven contract tests, tools like Pact are built exactly for this — each consumer team writes an executable expectation of the producer's contract, and the producer's CI runs those expectations against every change, which gives fast, automated feedback on breaking changes without building a permanent translation layer | | Contrast that with post-acquisition integration work, where a newly-merged company's legacy order or customer system genuinely fits the 'untrustworthy, uncontrolled, likely-to-be-decommissioned-eventually' profile | there, teams commonly do build a dedicated ACL, sometimes literally as a strangler-fig facade, specifically because the upstream is neither cooperative nor stable, and the isolation is worth its ongoing cost until that legacy system is retired |
- What signal would tell you it's time to add an ACL to an integration that started out as a trusted, jointly-owned relationship?The clearest signals are a change in control - the upstream team stops taking this consumer's input into account, gets reorganized, or the service is opened up to many more consumers with competing priorities - or a change in stability, like the upstream being slated for replacement/deprecation, or accumulating technical debt the consuming team explicitly doesn't want to inherit. At that point the original 'we can just agree on changes together' assumption no longer holds, and the isolation an ACL buys starts being worth its maintenance cost.
- How does consumer-driven contract testing reduce the need for an ACL between cooperating teams?It gives both sides automated, fast feedback the moment a producer's change would break a specific consumer's expectations, without requiring a permanent translation layer - the producer's CI pipeline runs the consumer's recorded expectations against every change, so incompatible changes are caught before deployment rather than discovered in production. This achieves much of the 'protect the consumer from unexpected upstream changes' goal an ACL provides, but through a lightweight verification process rather than an always-on translation component.
- Is there a middle ground between 'full ACL' and 'no protection at all' for a cooperating-but-not-permanently-cooperating integration?Yes - a thin adapter that just isolates the SDK/client dependency, so a version bump or transport change doesn't ripple through the whole codebase, without a full domain-model translation layer is a common middle ground, giving some insulation at low cost. Teams can also design the port/interface in their own vocabulary from day one without building a heavyweight translator behind it yet, making it cheap to add real translation later if the relationship changes.
Like hiring a professional translator for a business meeting with a close, longtime partner who already speaks your language fluently - it adds cost and a lag in every exchange for a risk that doesn't really exist yet; you'd only bring one in once the relationship changes, say when a new, less familiar party joins.
saying these in an interview costs you the question
- Treats ACL as a universal best practice with no cost-benefit reasoning
- Can't name any condition under which an ACL is NOT worth building
- No mention of contract testing or collaborative API design as an alternative for cooperating teams
- Doesn't recognize that translator maintenance against an actively co-evolving upstream is itself an ongoing cost
- No answer for what signal should trigger revisiting the decision later