beginner 3 min answer

A new notifications service needs customer contact data that only a twelve-year-old billing system holds. The team points it at a read replica of the billing database and ships in a week. Why does this work, and why is it still the most expensive integration they could have chosen?

integrationshared-databasecouplingschema-changeanti-corruption-layer
Show the full answer Hide the answer

What is being tested

Whether you can tell the difference between an integration that works and an integration that has a contract. Reading a replica works on day one precisely because it skips the step that costs money: agreeing what the data means and who is allowed to change it.

The mechanism

A database schema is an internal implementation detail. Nobody designed BILL_CUST_MSTR.EMAIL_2 to be an interface, and nobody documented what it means when it is blank, when it holds two addresses separated by a semicolon, or when the row is soft-deleted by a status flag that only the billing batch understands.

By reading the replica, the new service has silently promoted that schema to a published API without telling the team that owns it. The billing team still believes they own their tables, so the coupling is invisible from the side that would have to respect it.

The second effect is subtler. The new service now depends on the billing system's interpretation of its own data, reimplemented by people who have never read the batch code. Two implementations of "is this customer contactable" now exist, and they will drift.

What breaks first

Not performance. The first failure is a routine billing change that nobody thought was a change: a column widened, a status code added, a table split during a tidy-up. The billing team ships it in their normal release; the notifications service sends to a dormant address or stops sending at all, and the error surfaces as a customer complaint rather than as a failed deploy.

The second failure is that the replica becomes load-bearing. Once three more services read it, the billing team cannot restructure anything, which is exactly the constraint modernisation exists to remove. A shortcut taken to avoid touching the legacy system ends by freezing it.

The cheaper shape

  • One owned read interface. The billing team publishes a small API or a change-data-capture feed with named fields, and owns the mapping from its internal model to those names. Consumers never see a table.
  • A translation boundary on the consumer side for anything the legacy model gets wrong, so legacy concepts do not spread into the new service's domain.
  • A contract test that runs in the billing system's pipeline and fails there, not in production, when the published shape changes.

The cost is real: roughly a sprint of the legacy team's time, which is the scarcest resource in the building. That is the whole argument, and it is why the shortcut keeps winning.

When this is the wrong answer

If the legacy system is already scheduled for retirement inside a year and the new service is the thing replacing it, building a published interface on a corpse is waste. Read the replica, isolate the reads behind one module so the swap is a one-file change, and write down that you did it. The shortcut is defensible when it has a dated end.

Common weak answers

  • "Read replicas are fine because they are read-only." The risk is coupling to a schema, not write contention. Read-only changes nothing about who can change the columns.
  • "Add a view in front of the tables." Better than nothing, but a view owned by nobody and maintained by nobody is a contract only in appearance. What makes it an interface is that the owning team is accountable for it.
  • "Copy the data nightly instead." That trades schema coupling for staleness and still copies the schema. It helps only when paired with an owned, named extract.