Tangled connections contrasted with a clear modular path
Conceptual illustration

Unnecessary complexity rarely arrives with an obvious warning. It arrives as a helpful abstraction, a cache added before a bottleneck is understood, or a new service created to prepare for future scale. Each decision can sound reasonable on its own. The cumulative cost appears later, when an ordinary change needs knowledge of many components and several failure modes.

The aim is not to minimize the number of files or reject advanced tools. It is to make the amount of machinery proportional to a problem that exists and can be explained.

Count responsibilities, not diagram boxes

Consider an illustrative product catalogue. A database query is slow, so a shared cache is proposed. The new box on the diagram looks small. Its operational contract is not: someone must define freshness, expiration, invalidation, key structure, serialization, and behavior when the cache is unavailable.

Updating a product now involves at least two states. If the database commit succeeds and cache invalidation fails, users may continue seeing old data. If a cache entry expires during a burst, many callers may ask the database for the same value. The team also needs metrics, access control, and a plan for changing the cached data shape.

These are reasonable costs when a measured workload justifies them. They are expensive responsibilities to acquire merely because a cache is a familiar response to the word “performance.”

Find the smaller explanation first

Before adding infrastructure, identify what makes the request expensive. Is it issuing a query per item? Fetching rows the caller never uses? Missing an index that matches the filter? Repeating a calculation that can be moved out of a loop? A local change may remove the observed pressure without introducing another stateful system.

Measure representative input sizes and data distributions. A fast query on a tiny development database does not settle a production concern. Conversely, a large synthetic benchmark can distract from the endpoint that users actually wait for.

A useful comparison records the baseline, the specific change, the workload, and both latency and resource implications. An optimization that decreases response time while creating unbounded memory growth has moved the problem rather than resolved it.

Abstraction can make change cheaper or more obscure

A shared interface helps when several implementations genuinely serve the same responsibility. It becomes a burden when each caller must understand configuration switches for unrelated use cases. A generic “processor” that accepts dozens of flags may distribute domain rules across parameters that are difficult to reason about together.

Prefer a name that explains a business operation and an interface that exposes its actual inputs. Some duplication can be easier to maintain than an abstraction built around a similarity that turns out to be temporary. Review the pressure that would make a shared boundary valuable before committing every caller to it.

Write a decision with an exit condition

A short decision note keeps a technical choice open to evidence. For the catalogue cache, it might contain these fields:

  • Observed problem: the identified read path dominates a measured workload.
  • Alternatives checked: query shape, indexing, response size, and request-local reuse.
  • Accepted responsibility: a documented freshness window and an explicit fallback policy.
  • Verification: comparable measurements and a test of invalidation failure.
  • Revisit condition: a changed update rate, consistency requirement, or operational burden.

This note does not prove that the design is correct forever. It lets the next engineer see why the extra mechanism exists and which change in circumstances would weaken that reason.

Make removal part of the design

Temporary mechanisms tend to become permanent when nobody owns their retirement. A feature flag needs an owner and a point at which its unused branch can be removed. An adapter introduced for a migration needs a clear definition of completion. A duplicated write path needs reconciliation and a cutover plan.

Ask how the system will look after the transition ends. If the answer still includes every intermediate component, the plan may be accumulating layers rather than completing a change.

Use the model to challenge the extra component

In the Architecture Trade-off Simulator, add a cache or more application servers and inspect what happens at the actual bottleneck. A new component does not automatically improve the limiting station. The model has simplifying assumptions, but it is a useful way to practice asking what a proposed box changes and what it leaves untouched.

The clearest design is not always the smallest one. It is the one whose responsibilities can be explained, whose benefits are supported, and whose costs remain worth paying.