How do you distinguish a legitimate GRASP Pure Fabrication from an abusive "OrderManager" god-service that has drained the domain model of behavior?
answer
- one-sentence why-not-the-entity
- name = verb, not Manager
- describe job without "and"
- feature envy: uses only entity state
- anemic = invariants left unguarded
basics
~20 sA good fabrication has one clearly named job, a stated reason it can't live on a domain class, few dependencies, and leaves entities holding their own rules. A god-service has a vague name, many unrelated methods, and reduces entities to data bags.
solid answer
~50 sTest three things. **Motivation:** can you state in one sentence why no domain class should own this — "it needs SQL," "it converts to a wire format," "it spans three aggregates"? If the answer is "we always put logic in services," it is not a fabrication, it is habit. **Cohesion:** does the class have a single theme, and does its name describe an action or capability (`PriceCalculator`, `PdfRenderer`, `OrderRepository`) rather than a vague custodial noun (`OrderManager`, `OrderHelper`, `Utils`)? A class whose name only makes sense as "stuff about X" is a bucket. **Where the rules live:** if the entity has no invariants, no state transitions, and only getters/setters while a service mutates it from outside, you have an anemic domain model — behavior was moved that should have stayed. Also check dependency count, method-parameter patterns (services that always take the same entity and reach into it are misplaced methods), and whether the class can be unit-tested without half the system.
go deeper
Say a good invented class does one clearly named thing, while Manager/Helper classes that do many unrelated things and leave entities as data bags are the problem.
Give concrete criteria: single theme, action-based name, stated reason it can't be on the entity, and entities that still enforce their own rules.
Add feature envy detection, cohesion signals (shared fields, dependency count, mock-heavy tests), the domain-service vs application-service split, and a concrete refactoring sequence.
Talk about why the drift happens systemically (framework templates, layering dogma, transaction-script habits), how to detect it across a codebase, and what conventions or fitness functions keep the domain from being hollowed out.
## Why the distinction matters Pure Fabrication legitimises inventing non-domain classes. That legitimacy is easy to abuse: almost any behavior can be moved into a `SomethingService`, and each move looks locally defensible. The systemic result is the **anemic domain model** — entities reduced to data structures with getters and setters, and all rules spread across procedural services. The name comes from Martin Fowler's critique: it has the *cost* of object-oriented modelling (mapping, layering, ceremony) without the *benefit* (behavior encapsulated with the data it protects). So the practical skill is telling the two apart. ## Test 1 — Is there a stated, specific motivation? A fabrication should have a one-sentence justification of the form "this cannot live on a domain class **because**…": - …it requires infrastructure knowledge (SQL, HTTP, filesystem, crypto library). - …it converts between the domain and an external representation whose lifecycle differs. - …it coordinates several aggregates and no single one may own the others. - …it is a swappable algorithm the business wants to vary independently. - …it must be reused by contexts that should not depend on the entity. If the only justification is "our layering says logic goes in services," the class is a habit, not a design decision. ## Test 2 — Cohesion and naming Good fabrication names describe **what the class does**: `PriceCalculator`, `TaxRuleEngine`, `OrderJsonMapper`, `PasswordHasher`, `PaymentGateway`, `OrderRepository`. Bad names are custodial or empty: `OrderManager`, `OrderHelper`, `OrderUtils`, `OrderProcessor` (when "process" means five unrelated things), `BusinessLogic`. The name test is a *proxy* for cohesion. Ask: **can you describe the class's job without the word "and"?** `OrderManager` that validates, prices, persists, emails the customer, and writes an audit record fails immediately. Split it into `OrderValidator`, `PriceCalculator`, `OrderRepository`, `OrderNotifier`, `AuditLog`. Supporting signals of low cohesion: - Methods that share no fields with each other (classic **LCOM**, lack-of-cohesion-of-methods, symptom). - Constructor with a long, heterogeneous dependency list. - Sections of the class that different teams edit and that never change together. ## Test 3 — Feature envy: is it a misplaced method? A strong red flag is a service method that takes one entity and does nothing but read its fields and decide something: ``` class OrderService { isEligibleForFreeShipping(order) { return order.getTotal() > 100 && order.getCountry() == "DE" && !order.getItems().anyMatch(i -> i.isBulky()) } } ``` This is **feature envy**: the method is more interested in another object's data than its own. It uses only `order`'s state, needs no infrastructure, and would be a one-liner on the entity: `order.isEligibleForFreeShipping()`. Moving it back raises cohesion, shrinks the entity's public surface (fewer getters needed), and lets `Order` protect its invariants. Contrast with a genuine fabrication: `ShippingRateService.rateFor(order)` that calls an external carrier API. It also takes an `Order`, but it needs a network client the entity must not know about. ## Test 4 — Do entities still hold their invariants? Healthy split: - **Entity/aggregate:** state, invariants ("total never negative", "cannot add items to a shipped order"), state transitions (`ship()`, `cancel()`), calculations over its own data. - **Domain service (fabricated):** rules that genuinely span multiple aggregates or need external policy — e.g. `FundsTransfer` between two `Account`s. - **Application service (fabricated):** orchestration only — begin transaction, load via repository, call domain methods, save, publish events. No business rules. - **Infrastructure fabrications:** repositories, gateways, mappers, adapters. If an outside service performs `order.setStatus(SHIPPED)` after checking conditions itself, the invariant is unprotected — anyone can set any status. That behavior belongs on the entity. ## Test 5 — Testability and dependency shape A legitimate fabrication is usually easy to test: few dependencies, deterministic behavior, or one obvious seam to fake. A god-service needs six mocks and a paragraph of setup for each test — a direct measurement of its coupling. ## Quick checklist | Signal | Legitimate fabrication | God service | |---|---|---| | Reason to exist | one sentence, specific | "logic goes in services" | | Name | verb/capability | Manager/Helper/Utils/Processor | | Theme count | one | several, joined by "and" | | Uses only one entity's fields | rarely | often (feature envy) | | Entity behavior | intact, invariants enforced | getters/setters only | | Dependencies | few, coherent | many, heterogeneous | | Test setup | small | mock-heavy | ## Remediation When you find a god-service: (1) group its methods by the data they touch; (2) push back methods that use only one entity's state (feature envy); (3) split remaining groups into separately named fabrications; (4) reduce entity getters as behavior returns, so invariants become enforceable; (5) leave a thin application service that only orchestrates. Do it incrementally behind tests rather than as a big-bang rewrite.
- Is every *Service class an anemic-domain smell?No. Application services that only orchestrate (transaction, load, call, save) and domain services for genuinely multi-aggregate rules are legitimate. The smell is when services contain rules that use a single entity's data and that entity has none of its own behavior.
- How would you refactor an OrderManager with fifteen methods?Group methods by the data they use, push single-entity logic back onto the entity, extract cohesive clusters into named fabrications (validator, price calculator, repository, notifier), and leave a thin orchestrating application service. Do it incrementally under test coverage.
- Can metrics detect this automatically?Partly. Lack-of-cohesion-of-methods, dependency counts, class size, and change-coupling from version history flag candidates, but they can't tell a legitimate fabrication from a bucket. Use them as a search light, then judge by motivation and naming.
A specialist contractor you hire for one job — an electrician — is a good fabrication: clear name, clear scope, you know why you called them. A "general handyman" who ends up doing wiring, taxes, cooking, and driving is the god-service: nothing is clearly anyone's job, and the household forgets how to do anything itself.
saying these in an interview costs you the question
- "Pure Fabrication justifies putting all logic in services" — it justifies moving what has no domain owner, nothing more.
- Treating an anemic domain model as normal layering because a framework or template encourages it.
- Judging only by name: a well-named class can still be a bucket, and a badly named one can be genuinely cohesive — check the methods.
- Assuming any class taking an entity as a parameter is feature envy; the test is whether it needs anything beyond that entity's state.
- Fixing a god-service by renaming it rather than splitting responsibilities.