The Anti-Abstraction Manifesto

Every layer between application code and the work it performs is another boundary to understand and debug. Useful abstractions earn that cost.

Get this row

A typical web application makes a database query. Application code calls an object mapper, which builds SQL and passes it through a driver and a connection pool before the query reaches the database.

Between “get this row” and the work on the wire sit several interfaces, error models, maintainers, and release cycles.

When the query is slow, which layer do you profile? When the connection drops, which layer do you debug? When an advisory appears, which layer does it affect? The answer is usually “it depends,” followed by investigation.

The industry calls this separation of concerns. We call it separation of understanding.

What the layer costs

Abstractions should hide complexity. Often they move it to a boundary, where it shows up as mismatched data models, conflicting configuration, or behavior that neither side fully documents.

The end-to-end argument asks where a function can be implemented completely and correctly, then places it there. A boundary should follow the work, not the habit of adding another layer.

An object mapper is convenient until an unusual query requires SQL the developer has to inspect. A framework is convenient until its lifecycle fights the product. Once the application works around the abstraction, the team has to understand both the abstraction and the machinery beneath it.

Framework generations also replace one set of conventions with another. Applications either migrate or become “legacy,” which often means the team is afraid to touch them.

Layers add allocation, copying, and indirection. Errors hide under framework internals. Application source is only part of the specification; the rest lives in the mapper, the plugin, the convention.

A short path

Our application code calls library functions through a short path we own. Database queries stay visible. Routes and policies stay ordinary code. If an application needs a higher-level helper, we write one for the product in front of us rather than inherit a general framework.

When something goes wrong, the stack trace should contain code the team can inspect. There should be no plugin matrix to consult before the investigation can begin.

Short paths still need structure. Modules, types, and functions are abstractions too. They earn their place by naming a real idea and making the path easier to follow.

Frameworks can encode useful defaults and protect teams from familiar mistakes. Reimplementation is wasteful when an existing component is understood, stable, and a good fit. The right number of layers is the fewest that make the system clearer.

Every layer you add is a layer you may have to debug.

PreviousAnti-Complexity Manifesto NextAnti-Bloat Manifesto