GRASP's Information Expert says to give a responsibility to the class holding the needed data, which suggests putting a save() method on the Order entity itself. Why does Pure Fabrication argue for an OrderRepository instead, and what exactly is lost or gained?
answer
- Expert says data, cohesion says no
- two reasons to change
- dependencies point inward
- findById has no instance
- Active Record = opposite trade
basics
~20 sOrder holds the data, but saving needs SQL, connections, and transactions — infrastructure knowledge. Putting save() on Order mixes two unrelated jobs and ties the business class to the database. A separate OrderRepository keeps each focused and swappable.
solid answer
~50 sInformation Expert optimises for one force: put behavior where the data is. Pure Fabrication reminds you that cohesion and coupling can outweigh it. `Order.save()` makes `Order` responsible for two unrelated themes — business rules and storage mechanics — so the class now changes when the schema, ORM, or transaction strategy changes, not only when the business changes. It also drags infrastructure into every test of `Order`, and makes the entity depend on a connection/session it can't sensibly own. Extracting `OrderRepository` gives a class whose single reason to change is persistence, lets you substitute an in-memory or fake implementation in tests, allows several storage strategies behind one interface, and keeps the domain layer dependency-free. The costs are real: an extra abstraction to navigate, the risk of leaking query concerns into callers, and the temptation to strip more behavior from `Order` until it becomes an anemic data holder. Active Record deliberately makes the opposite trade for simple CRUD applications.
code
pseudocode · 15 lines// Domain layer — no infrastructure imports
class Order { items; total(); addItem(i) { ...business rules... } }
interface OrderRepository { // interface owned by the domain
save(order); findById(id): Order
}
// Infrastructure layer — the fabricated implementation
class SqlOrderRepository implements OrderRepository {
constructor(db, mapper) { ... }
save(order) { db.execute("INSERT/UPDATE orders ...", mapper.toRow(order)) }
findById(id) { return mapper.toDomain(db.query("SELECT ... WHERE id = ?", id)) }
}
// Tests of Order need no database; tests of the domain use InMemoryOrderRepository.go deeper
Say Order shouldn't know about SQL; a repository keeps business logic and storage separate and makes Order easy to test.
Name the collision (Information Expert vs cohesion/coupling), list concrete wins — single reason to change, isolated tests, swappable storage, findById has no instance — and mention Active Record as the alternative.
Add dependency inversion (interface in the domain, implementation in infrastructure), transaction/unit-of-work ownership, leaky-abstraction and query-method-explosion risks, and the anemic-domain failure mode.
Frame it as where the architecture draws its domain/infrastructure boundary, when the ceremony is not worth paying, and how to enforce the direction (module boundaries, architecture tests, dependency rules).
## The two principles in tension **Information Expert** (GRASP): assign a responsibility to the class that has the information needed to fulfil it. This is the default and it produces designs where behavior sits next to data — `order.total()`, `account.withdraw(amount)`. **Pure Fabrication** (GRASP): when the Expert assignment would damage cohesion, coupling, or reuse, invent a class with no domain counterpart to hold the responsibility instead. Persistence is the textbook collision. The `Order` object literally holds the fields you want to write to the database, so Expert points at `Order.save()`. Pure Fabrication overrules it. ## Why `Order.save()` is a problem 1. **Two reasons to change.** `Order` would change when pricing rules change *and* when the schema, ORM, connection pooling, or dialect changes. That is exactly the situation the Single Responsibility Principle warns about. Cohesion drops: the class now has a business theme and an infrastructure theme. 2. **Dependency direction inverts.** The domain — the most stable, most valuable code — starts depending on the most volatile, most replaceable code (a driver, an ORM session, a connection string). Layered, hexagonal, and Clean Architecture styles all forbid this direction: dependencies should point *inward* toward the domain. 3. **Testability.** Every unit test that touches `Order` now needs a database or a heavy mock of it. With a repository, the domain test constructs a plain `Order` and the repository is tested separately against a real database. 4. **Ownership of transactions.** Saving is rarely per-object. A business operation may write an order, its line items, and an inventory reservation in one transaction. An entity cannot sensibly own transaction scope; a repository plus a unit-of-work or an application service can. 5. **Reconstitution has no owner at all.** `findById(id)` cannot be an instance method on `Order` — there is no `Order` yet. That asymmetry is a strong hint that persistence is a *separate* responsibility. 6. **Multiple back-ends.** Read model in Postgres, cache in Redis, export to a file, in-memory for tests. An interface implemented by several fabricated classes handles this; a method baked into the entity does not. ## What the repository actually is A **repository** is a fabricated, behavior-only class that presents a collection-like interface (`add`, `remove`, `findById`, `findByCustomer`) over stored aggregates, hiding the storage mechanism. It typically holds collaborators (a data source, a mapper) but no business state. Its cohesion is high: one theme, persistence of one aggregate type. Its coupling is deliberately concentrated — it is the one place that knows both the domain and the database. A common refinement: declare the **interface** in the domain layer (`interface OrderRepository`) and put the **implementation** in the infrastructure layer (`SqlOrderRepository`). That is Dependency Inversion applied on top of the fabrication, and it is what makes the domain compile without any database library present. ## What you give up - **Indirection cost.** One more hop to read and understand; stack traces get longer; a trivial CRUD app may not repay it. - **Leaky abstraction risk.** If callers must know about lazy loading, N+1 queries, flush timing, or paging semantics, the fabrication has not really hidden the mechanism. - **Method explosion.** `findByX`, `findByXAndY`… repositories drift toward dozens of query methods. Specification objects or dedicated query services are the usual antidote. - **Anemic drift.** Having extracted persistence, teams often keep extracting until `Order` has only getters and setters and all rules live in `OrderService`. That is a failure mode, not the goal — Pure Fabrication removes what does *not* belong, it does not license removing what does. ## The legitimate opposite choice: Active Record **Active Record** is a pattern in which an object carries its own persistence (`order.save()`, `Order.find(id)`). It deliberately accepts the coupling in exchange for far less ceremony, and it is a good fit for CRUD-shaped applications with a schema that closely mirrors the objects. The design question is not "which is correct" but "is the domain complex enough that isolating it pays for the extra class?" With rich rules, many storage concerns, or a need to test the domain fast and in isolation, the fabrication pays. With simple table-per-class CRUD, it often does not. ## Answering the interview version Say: Information Expert is a default, not a law; when the responsibility drags infrastructure into a business class, cohesion and coupling win, so you fabricate a repository. Then name the concrete wins (single reason to change, inward dependencies, isolated tests, swappable back-ends, transaction ownership, `findById` has no instance to live on) and honestly name the costs (indirection, leaky abstractions, anemic drift) and the Active Record alternative.
- If the repository interface lives in the domain layer, isn't the domain still coupled to persistence?It is coupled to an abstraction it owns and defines, not to a database technology. The domain states what it needs ('give me an Order by id'); infrastructure supplies how. No driver, SQL, or ORM type appears in domain code.
- When is Active Record the better trade-off?CRUD-heavy applications where the object graph mirrors the tables, rules are thin, and delivery speed matters more than isolating the domain. It fails as business rules grow, as testing the domain without a database becomes important, or when multiple storage back-ends appear.
- Does moving persistence out of the entity automatically create an anemic domain model?No. Anemia comes from moving *business* behavior out too. The healthy split keeps invariants, calculations, and state transitions on the entity and puts only storage, mapping, and orchestration in fabrications.
saying these in an interview costs you the question
- "Information Expert says Order has the data, so Order.save() is correct" — treating a default as an absolute.
- Claiming repositories exist only to allow swapping databases; the primary wins are cohesion, dependency direction, and testability.
- Putting the repository *interface* in the infrastructure layer, which leaves the domain depending outward.
- Calling Active Record simply wrong instead of a different, sometimes better, trade-off.
- Assuming that extracting persistence requires stripping business behavior from the entity.