Rethinking 'Clean Architecture': Experts Decry Excessive Abstraction and 'Interface Tax'

The conventional wisdom that clean code and rapid development are mutually exclusive, often necessitating either ‘garbage code’ for speed or excessive boilerplate for correctness, has been called into question. A recent discourse highlights that many widely promoted ‘best practices’—such as adding layers, interfaces, and abstractions—frequently act as liabilities, hindering rather than accelerating development.

Central to this critique is the concept of an ‘interface tax,’ illustrated by the common pattern of abstracting third-party dependencies like a payment service (e.g., Stripe) behind an IpaymentService interface. The argument posits that if an interface has only one implementation and one usage, it provides no discernible value, merely introducing indirection. The often-cited justification of ‘future optionality’ (e.g., switching payment providers) is deemed insufficient, as such changes are often speculative and rarely imminent. Instead, a more pragmatic approach suggests directly using the third-party SDK within a specific handler. This strategy localizes change to a singular point, simplifying updates and refactoring if a dependency truly needs to be swapped. The speaker also dismisses testing as a primary rationale for immediate interface creation, pointing to virtual methods or library-provided fakes as viable alternatives for isolation.

Further extending the critique, the discourse argues that many practitioners applying methodologies like Clean Architecture, Onion Architecture, or Ports and Adapters often lose sight of their core objective: achieving isolation and managing the blast radius of changes through controlled dependencies. This oversight commonly leads to an over-application of layers and indirection, creating ‘five layers deep’ code paths for simple operations like database queries or API calls. For straightforward CRUD operations without complex business rules or invariants, the utility of patterns like repositories and rich domain models is challenged, advocating for direct use of ORM contexts. Ultimately, the emphasis shifts from eliminating coupling entirely—which is deemed impossible and undesirable—to managing coupling, often effectively achieved through ‘slices’ that define isolated boundaries for specific functionalities. This pragmatic architecture advocates for a senior approach where patterns are adopted not blindly, but based on a clear understanding of the value they provide and a genuine need for that value in a given context.