Enterprise Architecture Smells
Purpose
Turn architectural unease into findings with evidence, and stop the two opposite mistakes
that reviews make: declaring a smell because a structure is unfamiliar, and missing one
because every individual file looks reasonable.
A smell is not a defect. It is a symptom that warrants investigation, and it becomes a
finding only when a concrete harm can be named: a change that is riskier, a caller that can
break, a test that cannot be written, a cost that is being paid for nothing.
Workflow
- Observe, do not diagnose. Record what is actually there: file counts per change,
layer counts, imports, method bodies that only forward.
- Find the evidence in the history, not only in the code.
git log on a suspected god
class suggests whether unrelated features edit it; inspect the diffs and use cases to
distinguish cohesive work from competing responsibilities.
- Name the harm. Which future change is riskier? Which bug does this shape allow? If no
harm can be named, drop the finding rather than softening it into a nitpick.
- Cost the fix. Splitting costs navigation and wiring; adding an abstraction costs
indirection. If the fix plausibly costs more than the harm, record it as an observation
and say so.
- Check the acceptable case. Every smell in the catalogue has a situation in which it
is the right design. Check that situation before writing the finding.
- Order by impact and give the concrete first edit, not a target architecture.
Inspect the project's JDK, persistence namespace/version, Spring proxy/transaction configuration
and public contracts before recommending Java-specific edits. This skill has no executable
Java baseline; examples are partial shapes or policy snippets, not standalone programs. Do not
upgrade dependencies to apply them. If history or runtime evidence is unavailable, state the
missing evidence and a discriminating check; do not invent a confirmed finding.
The two failure modes of a review
False positive: "this violates the pattern"
A structure is unfamiliar or non-canonical, and the review
recommends a refactor with no named harm. Cost is real,
benefit is aesthetic.
False negative: "each file looks fine"
Every class is reasonable in isolation; the problem is in the
relationships. Trace representative changes and runtime paths;
file/layer/forwarding counts help select where to investigate.
Decision rules
An abstraction exists with one implementation and no inversion
→ inspect narrowing, policy, compatibility and extension contracts
before treating the indirection as waste (enterprise-base-patterns).
Adding a field touches many files
→ inspect why each changes: migrations, tests and independent
contracts can justify them. Investigate duplicate responsibility
and manual mapping drift (remote-facade-and-dto).
A class is edited by every feature team for unrelated reasons
→ investigate competing responsibilities and concrete change conflicts.
Rules are enforced in services and entities have only accessors
→ anaemic model — a finding ONLY if a domain model was the right
choice here (domain-logic-organization).
Two services must be deployed together
→ distinguish contract incompatibility from release policy or a
temporary migration; assess lost deployment independence and
actual operational cost (distribution-boundaries).
A persistence type appears in a controller signature or an API payload
→ inspect exposed fields, serialization and lazy access; mapping
annotations alone do not make every column the wire contract.
An abstraction's only stated purpose is hypothetical portability
→ compare present cost with a concrete supported migration contract;
do not assume future use or non-use (architecture-decision-making).
The codebase is unfamiliar but consistent, and change is cheap
→ not a finding. Consistency has value; personal preference does
not.
Rules
- Evidence first. Line counts, method counts and import counts are prompts to look at
the history, never findings by themselves. A cohesive 900-line class may be less costly
to change than a 200-line class serving conflicting responsibilities; verify actual harm.
- Files touched per feature is a screening metric. Group commits by actual feature/PR,
separate generated files, tests and migrations, and compare similar changes. Squashes,
formatting, renames and unrelated bundled work distort counts; corroborate with diffs,
incidents, lead time or repeated correction sites. Twenty matching commits is a sample,
not a confidence guarantee.
- An abstraction can remove duplication and accidental complexity as well as move costs.
Compare caller simplicity with configuration, maintenance and onboarding costs; do not
assume complexity is a conserved quantity.
- Anaemia is only a smell where a rich model was the right choice. Over transaction scripts
with a gateway, "entities with no behaviour" is the design, correctly applied
(
domain-logic-organization).
- A wrapper must justify its boundary. Translation, narrowing, policy, independent
ownership or compatibility may justify forwarding. Inspect annotations and interceptors
before concluding that an empty-looking body contributes nothing.
- Investigate both unnecessary patterns and missing boundaries. Ask "what would this cost
if we deleted it?" as well as "what is missing?"; neither category is a defect by itself.
- Change frequency affects maintenance priority, but stable modules can still impose
security, correctness, availability or operating costs. Rank observed harm and exposure
alongside change cost; stability alone neither requires nor rules out a fix.
- Do not recommend a target architecture. Recommend the next edit, with the harm it removes.
Wholesale rewrites are how a real finding becomes a six-month project that stalls
(
architecture-refactoring-paths).
- Distinguish accidental from essential complexity. Evaluate whether distributed consistency
is required before defending a saga, and whether shared repository behavior serves real
callers before rejecting genericity. Name the capability that removal would lose.
Finding format
Observation → harm → evidence/confidence → first edit → validation → what to avoid.
Observation: OrderService (3 240 lines, 11 collaborators) contains the pricing rules,
which also appear in QuoteService and the nightly re-rate job.
Harm: the last two pricing incidents were a rule changed in one of the three places.
The next rule change carries the same risk.
Evidence: git log shows 47 commits in 6 months from 5 teams; 9 touch pricing, and 6
of those touch exactly two of the three sites.
First edit: extract Pricing as a domain type with the discount chain, and have all
three call it. Do not move anything else.
Validation: preserve pricing outcomes at all three entry points, including rounding
and rejected inputs; verify that one representative rule change updates one policy site.
Avoid: splitting OrderService by layer first — that reshuffles the duplication
without removing it.
References
- Smell catalogue — each smell with symptoms, cause,
consequences, a detection command or query, the refactoring direction, and the situation
in which it is actually acceptable. Read when investigating a specific suspicion.
- Pattern overuse — the abstractions that cost more than
they return: interface-per-class, generic repositories, mapping chains, speculative
plugin points, premature services and premature domain models; with the questions that
decide whether an abstraction stays, and how to remove one safely. Read when deciding
whether something should exist at all.
1---2name: enterprise-architecture-smells3description: Detecting structural problems in an enterprise application from evidence, and telling genuine harm apart from unfamiliar-but-fine: anaemic domain models, god services, transaction-script sprawl, generic repositories, excessive layering and DTO mapping, leaky abstractions, distributed monoliths, persistence leakage, and abstractions that only move complexity. Use when reviewing an architecture or a large pull request, when adding a field touches seven files, when a "clean architecture" refactor is being proposed, when an interface has one implementation, when a wrapper adds no behaviour, when a pattern is being applied because it is a pattern, when a codebase feels wrong but nobody can say why, or when deciding whether an abstraction is worth keeping. Does not cover the migration once a smell is confirmed (architecture-refactoring-paths, legacy-enterprise-modernization), performance diagnosis (architecture-and-performance), or the individual patterns' own guidance.4---56# Enterprise Architecture Smells78## Purpose910Turn architectural unease into findings with evidence, and stop the two opposite mistakes11that reviews make: declaring a smell because a structure is unfamiliar, and missing one12because every individual file looks reasonable.1314A smell is not a defect. It is a **symptom that warrants investigation**, and it becomes a15finding only when a concrete harm can be named: a change that is riskier, a caller that can16break, a test that cannot be written, a cost that is being paid for nothing.1718## Workflow19201. **Observe, do not diagnose.** Record what is actually there: file counts per change,21 layer counts, imports, method bodies that only forward.222. **Find the evidence in the history**, not only in the code. `git log` on a suspected god23 class suggests whether unrelated features edit it; inspect the diffs and use cases to24 distinguish cohesive work from competing responsibilities.253. **Name the harm.** Which future change is riskier? Which bug does this shape allow? If no26 harm can be named, drop the finding rather than softening it into a nitpick.274. **Cost the fix.** Splitting costs navigation and wiring; adding an abstraction costs28 indirection. If the fix plausibly costs more than the harm, record it as an observation29 and say so.305. **Check the acceptable case.** Every smell in the catalogue has a situation in which it31 is the right design. Check that situation before writing the finding.326. **Order by impact and give the concrete first edit**, not a target architecture.3334Inspect the project's JDK, persistence namespace/version, Spring proxy/transaction configuration35and public contracts before recommending Java-specific edits. This skill has no executable36Java baseline; examples are partial shapes or policy snippets, not standalone programs. Do not37upgrade dependencies to apply them. If history or runtime evidence is unavailable, state the38missing evidence and a discriminating check; do not invent a confirmed finding.3940## The two failure modes of a review4142```text43False positive: "this violates the pattern"44 A structure is unfamiliar or non-canonical, and the review45 recommends a refactor with no named harm. Cost is real,46 benefit is aesthetic.4748False negative: "each file looks fine"49 Every class is reasonable in isolation; the problem is in the50 relationships. Trace representative changes and runtime paths;51 file/layer/forwarding counts help select where to investigate.52```5354## Decision rules5556```text57An abstraction exists with one implementation and no inversion58 → inspect narrowing, policy, compatibility and extension contracts59 before treating the indirection as waste (enterprise-base-patterns).6061Adding a field touches many files62 → inspect why each changes: migrations, tests and independent63 contracts can justify them. Investigate duplicate responsibility64 and manual mapping drift (remote-facade-and-dto).6566A class is edited by every feature team for unrelated reasons67 → investigate competing responsibilities and concrete change conflicts.6869Rules are enforced in services and entities have only accessors70 → anaemic model — a finding ONLY if a domain model was the right71 choice here (domain-logic-organization).7273Two services must be deployed together74 → distinguish contract incompatibility from release policy or a75 temporary migration; assess lost deployment independence and76 actual operational cost (distribution-boundaries).7778A persistence type appears in a controller signature or an API payload79 → inspect exposed fields, serialization and lazy access; mapping80 annotations alone do not make every column the wire contract.8182An abstraction's only stated purpose is hypothetical portability83 → compare present cost with a concrete supported migration contract;84 do not assume future use or non-use (architecture-decision-making).8586The codebase is unfamiliar but consistent, and change is cheap87 → not a finding. Consistency has value; personal preference does88 not.89```9091## Rules9293- **Evidence first.** Line counts, method counts and import counts are prompts to look at94 the history, never findings by themselves. A cohesive 900-line class may be less costly95 to change than a 200-line class serving conflicting responsibilities; verify actual harm.96- **Files touched per feature is a screening metric.** Group commits by actual feature/PR,97 separate generated files, tests and migrations, and compare similar changes. Squashes,98 formatting, renames and unrelated bundled work distort counts; corroborate with diffs,99 incidents, lead time or repeated correction sites. Twenty matching commits is a sample,100 not a confidence guarantee.101- An abstraction can remove duplication and accidental complexity as well as move costs.102 Compare caller simplicity with configuration, maintenance and onboarding costs; do not103 assume complexity is a conserved quantity.104- Anaemia is only a smell where a rich model was the right choice. Over transaction scripts105 with a gateway, "entities with no behaviour" is the design, correctly applied106 (`domain-logic-organization`).107- **A wrapper must justify its boundary.** Translation, narrowing, policy, independent108 ownership or compatibility may justify forwarding. Inspect annotations and interceptors109 before concluding that an empty-looking body contributes nothing.110- Investigate both unnecessary patterns and missing boundaries. Ask "what would this cost111 if we deleted it?" as well as "what is missing?"; neither category is a defect by itself.112- Change frequency affects maintenance priority, but stable modules can still impose113 security, correctness, availability or operating costs. Rank observed harm and exposure114 alongside change cost; stability alone neither requires nor rules out a fix.115- Do not recommend a target architecture. Recommend the next edit, with the harm it removes.116 Wholesale rewrites are how a real finding becomes a six-month project that stalls117 (`architecture-refactoring-paths`).118- Distinguish accidental from essential complexity. Evaluate whether distributed consistency119 is required before defending a saga, and whether shared repository behavior serves real120 callers before rejecting genericity. Name the capability that removal would lose.121122## Finding format123124Observation → harm → evidence/confidence → first edit → validation → what to avoid.125126> **Observation:** `OrderService` (3 240 lines, 11 collaborators) contains the pricing rules,127> which also appear in `QuoteService` and the nightly re-rate job.128> **Harm:** the last two pricing incidents were a rule changed in one of the three places.129> The next rule change carries the same risk.130> **Evidence:** `git log` shows 47 commits in 6 months from 5 teams; 9 touch pricing, and 6131> of those touch exactly two of the three sites.132> **First edit:** extract `Pricing` as a domain type with the discount chain, and have all133> three call it. Do not move anything else.134> **Validation:** preserve pricing outcomes at all three entry points, including rounding135> and rejected inputs; verify that one representative rule change updates one policy site.136> **Avoid:** splitting `OrderService` by layer first — that reshuffles the duplication137> without removing it.138139## References140141- [Smell catalogue](references/smell-catalogue.md) — each smell with symptoms, cause,142 consequences, a detection command or query, the refactoring direction, and the situation143 in which it is actually acceptable. Read when investigating a specific suspicion.144- [Pattern overuse](references/pattern-overuse.md) — the abstractions that cost more than145 they return: interface-per-class, generic repositories, mapping chains, speculative146 plugin points, premature services and premature domain models; with the questions that147 decide whether an abstraction stays, and how to remove one safely. Read when deciding148 whether something should exist at all.