skip to content

How does the "put behavior where the data is" idea behind GRASP's Information Expert scale up to module and service boundaries in a distributed system?

level: principalimportance: should knowfreq 26%

answer

  1. Feature Envy at service scale
  2. push the question, not the data
  3. intent endpoints, not entity CRUD
  4. replicate via events → eventual consistency
  5. chatty boundary = wrong boundary

basics

~20 s

The same rule applies to services: whichever service owns the data should own the logic and decisions about it. If one service constantly fetches another's data to decide something, the responsibility is in the wrong place — move the behavior to the data owner instead of moving the data around.

solid answer

~50 s

At class level Information Expert prevents Feature Envy; at service level it prevents chatty, distributed-monolith designs. The scaled rule: the component that is the *authoritative owner* of a piece of data should own the decisions requiring it, exposing an intent-shaped operation rather than raw reads. Symptom of violation: `OrderService` repeatedly calls `GET /customers/{id}` to evaluate a credit rule the customer service should own. That produces latency coupling, cascading failures, stale-read bugs, and deployment coupling — the distributed monolith. The fix is either to move the decision to the owner (`POST /customers/{id}/credit-check`) or to move an *authoritative copy* of the needed data via events and let the consumer decide locally, accepting eventual consistency. Caveats: cross-boundary rules that need several owners' data need an explicit decision — a saga/process manager, a read-model projection, or redrawn boundaries. And a boundary that forces constant chatter is usually evidence the boundary itself was cut in the wrong place, which is the more important finding.

go deeper

for a junior

Say the service that owns the data should make the decisions about it, so other services ask a question rather than downloading the data.

for a middle

Describe the chatty-reads symptom, the intent-shaped endpoint fix, and note that events plus a local replica are the alternative.

for a senior

Compare push-the-question vs push-the-data with their consistency, latency and availability consequences; name distributed monolith, saga, and CQRS projections as the tools.

for a principal

Treat chatter and change-coupling as evidence about the decomposition itself, argue for redrawing or merging boundaries, and connect the principle to the general rule of co-locating computation with authoritative state across storage, edge, and data-processing designs.

## Restating the class-level idea Information Expert (GRASP): assign a responsibility to whoever has the information needed. At class scope its purpose is low coupling and high cohesion, and its violation is visible as *Feature Envy* — code that uses another object's data more than its own. ## The same law at module/service scope Substitute "service" for "class" and "network call" for "method call" and every consequence gets sharper, because the call now costs milliseconds, can fail independently, and crosses a deployment and team boundary. **Rule:** the component authoritative for the data should own the decision. Publish *intent-shaped* operations (`reserveInventory`, `authorizePayment`, `checkCredit`), not raw table reads. ### What the violation looks like ``` OrderService: customer = GET /customers/{id} // pulls raw data history = GET /orders?customer={id} // pulls more raw data if (creditRuleOverAllThat) ... // decides in the wrong place ``` Consequences: - **Latency coupling.** Every order now costs N round-trips; p99 is the sum of dependencies' p99s. - **Availability coupling.** If Customers is down, Orders is down. Combined availability multiplies. - **Stale/torn reads.** Two separate reads may disagree; no cross-service transaction exists to make them consistent. - **Semantic coupling.** The credit rule is now duplicated wherever it is needed, and Customers cannot change its model without breaking readers — the definition of a **distributed monolith**: separate deployables that must ship together. - **Privacy/blast radius.** Raw customer data travels to services that only needed a yes/no. ### The two legitimate fixes 1. **Move the behavior to the data ("push the question, not the data").** `POST /customers/{id}/credit-check → {approved, limit}`. One call, one round-trip, the rule versioned and owned in one place, only the answer crosses the wire. Cost: an outbound synchronous dependency remains, so you need timeouts, circuit breakers, and a fallback policy. 2. **Move the data to the behavior, authoritatively.** Customers publishes `CustomerCreditChanged` events; Orders keeps a local, read-only replica and decides locally. Cost: **eventual consistency** — decisions can be made on slightly stale data, so you need compensations, and the replica is a cache with an ownership contract, not a shared database. Choosing between them is a consistency-vs-availability trade, not a style preference. Rule of thumb: if being seconds-stale is materially harmful (fraud, funds), call the owner; if it is tolerable and volume is high, replicate. ### What is explicitly *not* a fix - **A shared database** across services. It looks like "the data is right here", but it destroys ownership: schema changes become global, and every service becomes an expert on everyone's tables. - **A generic "data service"** that only does CRUD for others. That is the anemic domain model at architecture scale: all data on one side, all rules on the other, maximum chatter. ## When no single owner exists Some decisions genuinely need several owners' information ("can this order ship?" needs inventory, payment, and fraud). Options, with trade-offs: - **Orchestration (process manager / saga).** One coordinator calls each expert for its verdict and owns the workflow, not the rules. Clear, debuggable; the coordinator becomes a hot spot. - **Choreography.** Each service reacts to events and contributes its part. Loosely coupled; the end-to-end flow becomes hard to see and to reason about. - **Read-model projection (CQRS).** Build a purpose-shaped view fed by events for query paths, while writes still go through the owning expert. Excellent for reads, adds a consistency lag and a projection to operate. - **Redraw the boundary.** If two services are chatty on every request, they probably belong together. High chatter is *evidence about the decomposition*, and merging is an underrated, often correct answer. ## Diagnostics a principal should cite - Fan-out per business operation (calls per request); a rising number is Feature Envy at scale. - Ratio of read-only cross-service calls to command calls — many raw reads means logic sits away from its data. - Change-coupling from version control: services that always change together are one module in disguise. - Contract shape: services exposing entity CRUD invite envy; services exposing verbs keep expertise home. ## Bringing it back Information Expert is one instance of a general principle — *co-locate computation with authoritative state* — that also shows up as pushing predicates into the database instead of filtering in the app, as stored procedures vs. app logic (with their own maintainability trade-offs), as edge computing, and as data locality in distributed data processing. The unifying question is always: is it cheaper to move the question or to move the data?

  • If you replicate another service's data locally to decide without a network call, how is that different from a shared database?
    Ownership and direction. A replica is read-only, derived from published events, shaped for the consumer, and versioned by an explicit contract; the producer can change its internal schema freely as long as the event contract holds. A shared database gives every service write access to one schema, so there is no owner, no contract, and any change is globally breaking. The replica also makes staleness explicit and bounded, which the shared database hides.
  • What is the strongest signal that a service boundary was drawn in the wrong place?
    Chatter plus change-coupling: every business operation requires several synchronous round-trips to the same peer, and version history shows the two services almost always change in the same commit or release. That means the data and the rules over it were separated. The fix is usually to merge them or move the responsibility to the owner, not to add caching and retries around the symptom.

Instead of couriering a company's entire filing cabinet to your desk to answer one question, you phone the department that keeps the files and ask the question. If you need to ask a thousand times a second, you either get an official daily summary sent to you, or the two departments should have been one.

saying these in an interview costs you the question

  • Treating microservices as "data services + logic services" — the anemic model at architecture scale
  • Proposing a shared database as the way to keep behavior near data
  • Ignoring that replicating data buys availability by paying eventual consistency, and never naming the compensation strategy
  • Adding caches, retries and circuit breakers around a chatty boundary while leaving the misplaced responsibility in place
  • Assuming exposing entity CRUD endpoints is neutral; it actively invites callers to own other people's rules

context