At architecture scale, what are the costs of applying GRASP's Pure Fabrication liberally, and how would you govern its use across many teams?
answer
- every fabrication = a new word to learn
- eight files per feature
- duplicate mappers across teams
- catalogue the legitimate kinds
- interfaces on evidence, not on hope
basics
~20 sEach invented class adds a name to learn and a hop to follow. Overused, features spread across many thin files, entities go anemic, and reading code gets slow. Govern it with agreed roles, naming conventions, and an explicit reason for each new abstraction.
solid answer
~50 sThe cost of Pure Fabrication is paid in **navigability and vocabulary**: every invented class adds a term nobody outside the codebase knows, an indirection to trace, and a place where behavior could hide. At scale the failure modes are: a single feature touching eight files, entities drained to data holders, near-duplicate fabrications invented independently by different teams (`OrderMapper`, `OrderTranslator`, `OrderConverter`), and layer-crossing rules that force pass-through classes with no purpose. Governance is not "forbid fabrication" but **pre-name the legitimate kinds**: repository, gateway/port, adapter, mapper/assembler, domain service, application service, policy/strategy, factory. Anything outside the catalogue needs justification. Reinforce with naming rules (verb-or-capability, never `Manager`/`Helper`/`Utils`), architecture tests that enforce dependency direction, code review asking "which domain class could own this, and why can't it?", and periodic reviews using change-coupling data to find abstractions that never varied. Prefer directness by default; fabricate on evidence.
go deeper
Note that each extra class is one more thing to find and understand, so invent one only when there is a clear reason.
List concrete costs — more files per feature, longer call chains, duplicate mappers — and the habit of stating a reason before creating a class.
Add anemia drift, speculative generality, pass-through layers, and practical controls: naming rules, architecture tests, and the "which class could own this" review question.
Frame it as budgeting comprehension against decoupling: a published catalogue of fabrication kinds, automated dependency enforcement, evidence-based interfaces, boundary-focused spend, and history-driven pruning of abstractions that never varied.
## Restating the principle before critiquing it **Pure Fabrication** (GRASP): assign a cohesive responsibility to an invented class with no counterpart in the business domain, when assigning it to a domain class would damage cohesion, coupling, or reuse. Typical products: repositories, mappers, gateways, calculators, policies. At the scale of one class, the principle is almost always right. At the scale of a system with hundreds of developers, applying it *reflexively* has systemic costs that no single review catches. ## The costs ### 1. Vocabulary inflation Domain classes are free to learn — the business already uses their names. Every fabrication introduces a term that exists only inside the codebase. Ten fabrications are a design; a thousand are a private language that new joiners must absorb before they can read a feature. This is the hidden onboarding tax. ### 2. Navigability and "where does it actually happen" Each fabrication adds a hop. A request that once read `Controller → Order` may become `Controller → CommandHandler → ApplicationService → DomainService → Policy → Repository → Mapper → Row`. Every hop is defensible in isolation; the aggregate is a call chain nobody holds in their head. Symptoms: stack traces dominated by framework and glue frames; debugging sessions spent stepping through delegation; "shotgun surgery" where one change edits eight files. ### 3. Anemia by a thousand extractions Each extraction "just moves this one rule out." Cumulatively the entity keeps only getters and setters. Once invariants are unenforced, correctness depends on every caller remembering the rules, which is exactly what encapsulation was meant to avoid. ### 4. Uncoordinated duplication Different teams fabricate for the same responsibility with different names — `OrderMapper`, `OrderTranslator`, `OrderConverter`, `OrderAssembler` — with subtly different semantics. Searching becomes unreliable; behavior drifts. ### 5. Speculative generality Interfaces created "in case we swap it" that never get a second implementation. They cost navigation and mocking ceremony, and they often *harm* design by freezing an early guess at the right seam. ### 6. Pass-through layers Strict layering rules can force a fabrication per layer whose only job is to forward a call and re-map identical fields. This is fabrication as bureaucracy: cost with no cohesion gain. ## Governance that works ### a) Pre-name the legitimate roles Publish a small catalogue of fabrication *kinds* with a one-line definition and an example each: | Kind | Responsibility | Belongs to | |---|---|---| | Repository | load/store an aggregate, collection-like API | domain interface, infra implementation | | Gateway / Port | talk to an external system behind our own types | domain interface, infra adapter | | Adapter | translate an external API to our port | infrastructure | | Mapper / Assembler | convert between representations | boundary | | Domain service | rule spanning multiple aggregates | domain | | Application service / handler | orchestrate a use case; no business rules | application | | Policy / Strategy | a rule the business varies deliberately | domain | | Factory | non-trivial construction | domain or infra | A new fabrication that fits a catalogue kind needs no debate. One that fits none needs an explicit justification — that is the throttle. ### b) Naming rules with teeth Names must state a capability or action (`PriceCalculator`, `InvoiceRenderer`) — never `Manager`, `Helper`, `Utils`, `Processor`, `Handler` used as a generic bucket. Enforce by convention plus review; some teams enforce a deny-list in static analysis. ### c) Automate what is mechanical **Architecture/fitness tests** (dependency rules expressed as executable checks) can enforce: domain must not import infrastructure packages; repository interfaces live in the domain; adapters only referenced through ports; no cycles between modules. These catch the direction errors that reviews miss. They cannot judge cohesion — that stays human. ### d) The review question One standing question in code review: **"Which existing class could own this, and what specifically breaks if it does?"** If the answer is "nothing breaks, it's just our style," don't fabricate. This single prompt prevents most reflexive extraction. ### e) Interfaces on evidence Default to a concrete class. Introduce an interface when there is a second implementation *now* (including a test fake that a real seam requires), a vendor genuinely likely to change, or a published boundary between teams. "Might change" is not evidence. ### f) Periodic pruning with data Use version-control history: abstractions whose implementations never diverged, interfaces with one implementation for years, and mappers between identical shapes are candidates for inlining. **Change coupling** (files that always change together) reveals fabrications that split something that was never really separate. ### g) Boundaries matter more than classes At architecture scale the decisive fabrications are the ones on module and service boundaries — ports, gateways, anti-corruption layers. Inside a module, allow local directness. Spend the decoupling budget where the blast radius of a change is large; economise where it is small. ## The balanced stance Pure Fabrication removes what does not belong in the domain — it is not a mandate to relocate everything. The senior instinct is to fabricate; the principal instinct is to ask what the fabrication *buys* against a budget of comprehension. State the trade explicitly: decoupling and testability on one side, vocabulary, hops, and shotgun surgery on the other. Then set defaults that make the cheap choice the common one and the expensive choice a deliberate, documented one.
- How do you decide whether an interface is justified or speculative?Require evidence: a second implementation exists now, a vendor is genuinely likely to be replaced, or the boundary is published across teams. A test double alone is weak evidence when the class is already easy to construct. Otherwise start concrete — extracting an interface later is cheap and mechanical.
- Can static analysis detect over-fabrication?Only partially. It can enforce dependency direction, ban bucket names, flag single-implementation interfaces, and surface class-count growth. It cannot judge whether a responsibility had a natural domain owner, so the review question stays human.
- How do you undo an over-fabricated codebase without a rewrite?Work by use case: trace one flow, inline pass-through layers that only forward, merge duplicate mappers onto a single agreed name, push single-entity rules back onto entities, and collapse single-implementation interfaces. Each step is small and test-covered; publish the catalogue so new code stops adding to the pile.
Fabrications are like intermediaries in a supply chain. A few specialists make the whole chain more efficient and resilient. Add one at every step and nothing is wrong locally, yet an order now passes through fifteen desks — slower, harder to trace, and no one can say where a decision was actually made.
saying these in an interview costs you the question
- "More abstraction is always safer" — abstraction has a running comprehension cost paid by every reader.
- Treating class count or layer count as a quality metric.
- Adding interfaces preemptively for every fabricated class in case of future change.
- Believing architecture tests can judge cohesion; they only enforce structure and direction.
- Solving over-fabrication with a rewrite instead of incremental inlining and a published catalogue of allowed roles.