advanced 3 min answer Multiple choice

A procurement platform's anti-corruption layer maps a vendor's supplier codes to internal supplier ids and caches the mapping indefinitely because the codes never change. At 09:14 new purchase orders start appearing under a supplier that was deactivated 14 months ago - with that supplier's name, bank details and contract terms. The payload validates, nothing errors, and the vendor reports no incident. Which design decision made this possible?

anti-corruption-layeridentityintegrationsilent-failurelegacy
Pick one
Show the full answer Hide the answer

The trigger

The vendor reuses supplier codes. Its code space is finite, deactivated suppliers free their codes, and a new supplier was issued one that a retired supplier used to hold. The anti-corruption layer's mapping table has one row per external code, written the first time that code was seen, so the resolver did exactly what it was built to do and returned the internal id of a company that no longer trades.

Why it propagated and why detection lagged

Every downstream check passed. The payload was well-formed, the internal id existed, the supplier record was complete, foreign keys resolved. There is no schema in which this is invalid — the data is correct, it is about the wrong company.

Detection needs a semantic signal: bank details that do not match the invoice, a contract term that nobody negotiated, a buyer noticing a familiar name. An integration that has been in production for years accumulates history, and fourteen months of legitimate activity behind that internal id makes the wrong link look authoritative to anyone who checks. At a few hundred purchase orders a day against an active supplier, a week of lag is thousands of rows to unwind by hand, and some of them have already been paid.

Why the other options fail

  • The cache. Expiring it changes nothing. A fresh lookup of a recycled code resolves to the same wrong internal id, because the mapping table is the thing that is wrong. The cache only sets how long you keep believing it, which is why "add a TTL" is the tempting local fix that fixes nothing.
  • Translating rather than storing raw. Translation is the layer's job and is what kept the vendor's codes out of the domain. Storing the raw model would have spread the same identifier further, not made it safer.
  • Deployment topology. In-process or separate service, the same mapping produces the same result. Identity semantics are not a packaging question.

The structural fix versus the tempting local fix

Mint internal identity from an immutable corroborating attribute plus a validity interval, not from the external code alone. In practice: key the mapping on (external code, first-seen), store a registration or tax number alongside it, and treat a change in that corroborating attribute on an unchanged code as a new external entity until a human confirms otherwise. Add a reconciliation job that re-reads the vendor's active supplier list every 24 hours and flags any code whose corroborating attributes no longer match the mapping — that is the signal that would have fired on the first day instead of fourteen months in.

The rule worth carrying out of this: an anti-corruption layer that translates identity must treat every external identifier as unique only within a window. Names, codes, email addresses and account numbers are all recycled by somebody.

When this is the wrong answer

When the external system contractually guarantees globally unique, never-reused identifiers — a UUID, a national company register number, an issuer-assigned identifier with a published non-reuse policy. Choose the flat mapping table only if that guarantee is written down somewhere you could point a regulator at; a support engineer's assurance that codes never change is not that. The question to ask the vendor is exactly one sentence: what happens to a code when a supplier is deleted? If the answer is anything other than "it is never issued again", you need the window.