What is a named fetch plan in a data-access layer, and when is one better than naming the fetches inside each query?
answer
- a shape, declared once
- attach where there is no query text
- named after the use case
- plans widen, they never narrow
- reuse by a narrower caller is the trap
basics
~20 sA named fetch plan is a reusable, named description of which links a read should materialise, declared once and attached to a call. It beats inline fetches when several call sites share one shape, or when the read has no query text - a load by identifier.
solid answer
~50 sA **fetch plan** lists the links a particular read should materialise; naming it lets the same list be declared once and attached wherever that use case is served. Inline fetching says the same thing in the query text itself. The plan wins in three situations: when several reads need the identical shape and you do not want the list copy-pasted; when the read is a plain load by identifier, which has no query text to hang a join on; and when the shape deserves a name that states intent, such as the graph an invoice needs. Its costs are real too - reading a query no longer tells you what it loads, and a plan reused by a new caller that needs less becomes a quiet over-fetch. Plans are also additive in practice: they widen what a read pulls, and do not remove links the mapping already declared eager.
go deeper
Know that a fetch plan is just a named list of links a read should bring back, attached to a call, and that it affects only the reads it is attached to.
Explain the two expressions - inline in the query versus named and attached - and give the case an inline fetch cannot cover: a load by identifier, which has no query text.
Show you police reuse. A plan adopted by a caller that needs less is a silent over-fetch, so tie each plan to a use case and re-check it when the mapping changes.
Weigh the indirection against the duplication. Named plans centralise a cost decision away from the call site; that is worth it when shapes are shared, and harmful when it hides what an endpoint pulls.
## What a fetch plan is A **fetch plan** is a description of which links a single read should materialise, and how deep to follow them. It is attached to a read rather than written into the mapping, so it changes one call's behaviour without committing every other read of the same object. A plan can be expressed two ways: - **Inline**, inside the query itself - the read says, in its own text, which links to fetch alongside the result. - **Named**, declared once next to the mapping or in a plan registry, then referenced by name at call sites. Both reach the same runtime outcome. The difference is where the list of links lives and how many callers share it. ## Why naming a plan earns its keep | Situation | Why an inline fetch struggles | What the named plan gives | |---|---|---| | Several reads need the same shape | the list is copy-pasted and drifts apart | one definition, one place to change | | The read is a load by identifier | there is no query text to attach a fetch to | the plan attaches to the call itself | | The shape is a use case, not a query | the intent lives nowhere | the name states it: the graph an invoice needs | | A shape is deep - a link under a link | the query text grows and blurs | nesting is expressed once, explicitly | The load-by-identifier case is the one people miss. Fetching a link inline requires a query to write it in. When the application simply asks for an object by its key, only a plan attached to that call can widen the read; otherwise the mapping default is all there is. ## What naming costs 1. **Indirection.** The query text no longer tells you what the read pulls. Someone diagnosing a heavy endpoint must find the plan, and then the plan's nested branches, before they know what was materialised. 2. **Reuse drift.** A plan built for the widest caller gets attached by a new, narrower caller because it is *there*. Nothing fails; the read simply pulls more than the screen shows. This is the most common way plans turn into over-fetch. 3. **Proliferation.** Naming plans per use case is the discipline that keeps them honest, and it produces many small plans. That is usually the right trade, but it is a maintenance surface. 4. **Double specification.** A plan attached to a query that already joins the same link can duplicate the instruction, and which one governs is layer-specific rather than obvious. ## Plans widen; they do not narrow A plan adds links to what a read materialises. It generally cannot remove a link the mapping declares eager, because that declaration is treated as part of the object's definition. Two consequences: - The narrowest possible read of a mapped object is the mapping's own defaults, not the emptiest plan you can write. - If reads keep coming back too fat even under a tight plan, look at the mapping. The plan is not the thing making them fat. ## Choosing between inline and named A workable rule of thumb: - **One caller, one shape:** inline. The cost is written where the read is, and there is nothing to look up. - **Several callers, one shape:** name it, and name it after the use case rather than after the links it happens to contain - `order-for-invoice` survives a change of contents, `order-with-lines-and-customer` does not. - **No query text at all:** a plan is the only option. - **A shape used once but deep:** name it anyway if the nesting makes the query unreadable. ## Keeping plans honest over time - Attach the plan at the use case, not in a shared helper that hides which callers inherit it. - When a new caller wants an existing plan, check what it needs against what the plan pulls; "close enough" is how a plan grows a second job. - Review plans when the mapping changes: a link that becomes eager on the mapping makes part of a plan redundant, and a link that becomes deferred may leave a plan silently short. - Treat an unexplained widening of a plan the way you would treat a widened query: someone now pays for those bytes on every read that uses it. The underlying idea is simple. What a read should materialise is a property of the *use case*, not of the object; a named plan is just the artefact that lets you say so once and reuse it, at the price of putting the answer one lookup away from the query.
- What should a fetch plan be named after?The use case it serves, not the links it currently contains. A name like the graph an invoice needs stays accurate when a link is added or dropped; a name that enumerates its contents becomes a lie after the first edit, and encourages other callers to reuse it because the contents sound convenient rather than because the use case matches.
- How does a plan interact with links the mapping already declares eager?It adds to them. The mapping defaults form the floor of what a read materialises, and the plan widens from there. A plan cannot leave out an eager link, so if reads are still heavier than the plan explains, the mapping is where the extra weight is declared.
saying these in an interview costs you the question
- Thinks a fetch plan can exclude links the mapping declares eager.
- Names plans after their contents, so the name rots on the first edit.
- Reuses one wide plan everywhere because it already exists.
- Believes a plan changes the mapping default for other reads.
- Cannot say why a load by identifier needs a plan rather than an inline fetch.