skip to content

How does the GRASP Information Expert principle relate to "Tell, Don't Ask" and to the anemic domain model anti-pattern?

level: middleimportance: must knowfreq 55%

answer

  1. Expert = placement, Tell-Don't-Ask = call style
  2. anemic = getters/setters + fat services
  3. withdraw() not getBalance()/setBalance()
  4. Feature Envy → Move Method
  5. read models/CQRS are a legitimate exception

basics

~20 s

Information Expert says the object holding the data should do the work. That is exactly what "Tell, Don't Ask" asks for: call a method on the object instead of reading its fields and deciding outside. When you break both, entities become data bags with all logic in services — the anemic domain model.

solid answer

~50 s

The three are the same idea seen at three levels. **Information Expert** is the *placement rule*: assign the responsibility to whoever holds the information. **Tell, Don't Ask** is the resulting *call style*: instead of `if (account.getBalance() >= amount) account.setBalance(account.getBalance() - amount)`, you write `account.withdraw(amount)`. You can only "tell" when the behavior lives on the expert; otherwise you are forced to "ask". **Anemic domain model** is what you get by systematically ignoring both: entities with only getters/setters, and all rules in `XService` classes. Fowler calls it an anti-pattern because it pays the cost of an object model (mapping, identity, lifecycle) while getting none of the benefit — invariants are unenforced, logic is duplicated across services, and every rule change touches multiple files. Caveats: Tell-Don't-Ask is not "no getters ever" — queries and read models legitimately expose state; and functional styles achieve the same locality with data + module-scoped functions rather than methods.

code

pseudocode · 10 lines
pseudocode
// Ask: caller owns the rule; Account is a data bag (anemic)
if account.getStatus() == ACTIVE and account.getBalance() >= amount:
    account.setBalance(account.getBalance() - amount)

// Tell: Account is the Information Expert and owns its invariant
class Account:
    withdraw(amount):
        require(status == ACTIVE, "inactive account")
        require(balance - amount >= overdraftLimit, "insufficient funds")
        balance -= amount

go deeper

for a junior

Show the withdraw() vs getBalance()/setBalance() contrast and say why the second lets callers break the rules.

for a middle

Articulate the chain: Expert places behavior, which makes 'tell' possible, and skipping it yields an anemic model with duplicated, unenforced rules.

for a senior

Add the boundaries: cross-aggregate operations, read models/CQRS, and layer-crossing concerns all legitimately live outside the entity; explain how the application service becomes orchestration-only.

for a principal

Discuss it as an invariant-ownership strategy: which component is authoritative for which rule, how that maps to aggregate and service boundaries, and the migration path from an anemic legacy model without a big-bang rewrite.

## Defining each term without assuming prior exposure **Information Expert (GRASP).** A heuristic: give a responsibility to the class that already has the information needed to carry it out. GRASP = *General Responsibility Assignment Software Patterns*, a vocabulary from Craig Larman for reasoning about where behavior goes. **Tell, Don't Ask.** A style rule (Alec Sharp, popularized by the Pragmatic Programmers): rather than querying an object for its state, making a decision, and then pushing the result back, ask the object to perform the decision itself. **Anemic domain model.** A term from Martin Fowler for a design where domain classes hold state but almost no behavior; the rules live in procedural "service" classes that manipulate those states from the outside. ## The causal chain Information Expert is the *cause*, Tell-Don't-Ask is the *observable effect*, anemia is the *disease of the opposite*. ``` Expert followed -> behavior sits on the data owner -> callers can 'tell' -> rich model Expert ignored -> behavior sits elsewhere -> callers must 'ask' -> anemic model ``` Concretely: ``` // Ask (Expert violated): the rule lives in the caller if (account.getStatus() == ACTIVE && account.getBalance() >= amount) { account.setBalance(account.getBalance() - amount); account.setLastActivity(now); } // Tell (Expert honored): the rule lives with the data account.withdraw(amount); // throws / returns a result if not permitted ``` Everything the withdrawal rule needs — status, balance, activity timestamp — is inside Account. Account is the expert; the rule belongs there. Once it does, the caller has nothing left to ask for, and `setBalance` can disappear from the public surface entirely, which means no other caller can ever put the account into an invalid state. ## Why anemia is costly (the argument you should be able to make) 1. **Invariants are unenforceable.** If balance is settable from outside, no single place guarantees "balance never goes below the overdraft limit". With `withdraw()` as the only mutator, the invariant is structurally guaranteed. 2. **Duplication.** The same eligibility check gets re-implemented in a REST service, a batch job, and an admin tool, and they drift. 3. **Shotgun surgery.** Changing one rule touches every service that inlined it. 4. **Poor discoverability.** A newcomer reading `Account` learns nothing about accounts. 5. **You pay ORM/mapping cost for nothing** — the classic Fowler objection. ## The honest counter-arguments - **Not all state should be hidden.** Read paths, reporting, serialization, and CQRS query models legitimately expose data. Tell-Don't-Ask is about *decisions*, not about forbidding accessors. - **Some logic genuinely spans entities.** A transfer touches two accounts; neither is the expert on the pair. A domain service (a Pure Fabrication) is the correct home — that is not anemia, because the service holds *coordination*, not the accounts' own rules. - **Functional and data-oriented designs** invert the mechanics: immutable records plus module-level functions. They still honor the spirit — the function that knows the shape owns the operation, and the operation is not scattered — but they do not use method dispatch to get there. Saying "functional code is anemic by definition" is a red flag. - **Layer-crossing concerns** (persistence, transport, authorization at the edge) are deliberately kept *off* the expert. ## Practical detection - Search for sequences of getter calls followed by a setter on the same object — that pattern is a Tell-Don't-Ask violation and usually an Expert violation. - Look for **Feature Envy**: a method using another object's data more than its own. The refactoring is *Move Method* — literally applying Information Expert after the fact. - Long chains like `a.getB().getC().getD()` also violate the **Law of Demeter**; the Expert fix is to push the operation down the chain so each hop only talks to its neighbour. ## A rule of thumb for interviews "Information Expert tells me *where* to put it, Tell-Don't-Ask tells me *how the call should read* once I have, and an anemic model is what I see when neither was applied."

  • Doesn't a rich domain model conflict with layering, since the entity now contains business rules that the service layer used to hold?
    No — it changes what the service layer is for. Application services keep orchestration: transactions, security, fetching aggregates, publishing events, calling other systems. The invariants move into the entities. The service gets thinner and more uniform, which is the point; layering is about direction of dependency, not about which layer hoards the rules.
  • Where does a money transfer between two accounts belong, if neither account is the expert?
    In a domain service — a Pure Fabrication such as TransferService — because the responsibility needs information from two aggregates. Each account still owns its own half (debit/credit with their own invariants); the service coordinates and owns the atomicity/consistency policy. That is not anemia, since the accounts retain their rules.

Asking is handing a doctor your chart and diagnosing yourself out loud; telling is saying "treat me" and letting the one who owns the medical knowledge decide.

saying these in an interview costs you the question

  • "Tell, Don't Ask means never write a getter" — read paths and query models legitimately expose state
  • Calling any functional or data-oriented design anemic purely because behavior is in functions rather than methods
  • Treating a rich model as forbidding all service classes — orchestration and cross-aggregate policy still need them
  • Moving persistence and transaction handling into entities in the name of Information Expert
  • Believing anemia is only a naming problem, rather than a loss of enforced invariants

context