concept

Concern Restatement Tax

also called Layer Tax, Shotgun Surgery

The fixed, permanent cost paid by every feature when architectural layers restate the same concern at several altitudes instead of separating different concerns.

separation-of-concernslayeringlineardelivery-speedboilerplate

Add one field to one entity. Count the files that must change. In a design that separates concerns, the answer is one to three: the schema, an index if performance requires it, and a policy if the field needs one.

When the answer is nine — DTO, entity, mapper, repository interface, implementation, service, controller, view model, template — the layers are not separating concerns. They are restating one concern at four altitudes.

Why it matters

This is the single largest determinant of delivery speed in most codebases, and it is almost never what teams spend design time on. It is a fixed tax on every feature for the life of the system, and unlike a distributed-systems problem it never produces an incident, so nothing ever forces it onto the roadmap.

It also produces bugs. Every restatement is a place a change can be forgotten, which turns a mechanical edit into a correctness risk.

Implementation patterns

  • Use the small-change count as a routine architecture assessment. It needs no diagram, no interviews and no tooling, and it is difficult to argue with.
  • Also measure the cross-cutting change — adding a tenant identifier to every audit record, or a new authorisation dimension. If cross-cutting changes are cheap and single-field changes are expensive, the separation is inverted: you have layered by mechanism where you needed to layer by concern.
  • Delete layers that only translate. A mapper that copies fields one-to-one between two identical shapes is not an abstraction boundary; it is a place to make mistakes.
  • Let generic machinery stay generic. In a sync engine, the transport should not know what an issue is; if adding a domain field requires editing the sync code, domain knowledge has leaked into transport and every future field pays for it.

Industry example

A local-first product such as Linear runs a client store, a sync engine and a server. Adding a field to an issue should touch three things at most: the schema, any index the field needs to be queried by, and its conflict-resolution policy — because conflict policy is a legitimate per-field concern, and pretending otherwise is the more common mistake. A last-writer-wins title and a set-union label list genuinely differ, and the honest design declares that in the schema rather than burying it in merge code.

If the sync engine has to change, the boundary between domain and transport has failed. If a UI component has to reason about pending mutations, the boundary between store and sync has failed.

Failure scenarios

  • Layers adopted by convention, applied uniformly whether or not each one has a distinct responsibility.
  • Anaemic layers that exist to satisfy a template and carry no logic.
  • Domain knowledge leaking into generic machinery, which converts a one-line schema change into a change to the transport.
  • The tax being invisible to management, because it never causes an outage — only a slow, unattributable decline in throughput.

Trade-offs

Some restatement is deliberate and correct. A public API contract that deliberately differs from the internal model is a boundary worth maintaining by hand, because it decouples your release cadence from your customers'. The distinction is whether the two shapes differ on purpose or merely differ because a template said so.

Collapsing layers also has a cost: it couples the persistence shape to the wire shape, so a database change becomes an API change. That is a fair trade in an internal service and a bad one in a public API.

Interview question

"Take a feature you shipped recently that added one field. How many files changed, and for each layer, tell me what distinct concern it was separating. Which ones would you delete?"