Decode: layering, boundaries, and naming
Three of Rai’s reviewers pushed the same underlying idea from different angles: logic belongs to the layer that owns it, and decisions get made at the boundary. This lesson decodes all three, because they’re one principle.
The comment (Nisha Nambissan, PR #491, on a frontend retry):
“the pattern of retrying on the browser side looks like an antipattern. This should not have been a frontend concern in the first place. We have surfaced it backwards and made it a client concern, when it should have stayed in the backend and been addressed there.”
And (Nick Ferrell, PR #355, on a nullable date threaded downstream):
“Why allow null at all? Doesn’t it just use the current date anywhere? I would think about making this explicit upstream rather than buried downstream.”
And (Nick, same PR, on the query store):
“nit: the querystore should NOT be making this business decision.”
What they meant. A resilience concern (retrying a flaky dependency) is owned by the backend, which makes the call; pushing it to the browser inverts the layering and, worse, stacks client retries onto a cold-start timeout. A business default (what “no date” means) is owned by the boundary, the endpoint; defaulting it with ?? today deep in the query store buries a product decision in the persistence layer, where it’s invisible and untestable. Both are the same rule: push each decision to the layer that owns it, and make it explicit at the edge.
// What Nick flagged: a nullable date threaded down, defaulted deep inside.
public Task<FlightsView> Execute(DateOnly? day) { var d = day ?? Today; /* ... */ }
// The fix: the endpoint resolves the default; Execute takes a non-null DateOnly.
public Task<FlightsView> Execute(DateOnly day, CancellationToken ct) { /* ... */ }
// endpoint: useCase.Execute(day ?? DateOnly.FromDateTime(clock.UtcNow), ct)And the boundary-validation flavour of the same idea, from Nick on PR #397:
“Data should be cleaned/valid before being sent/used in the domain.”
Rai’s fix: “the query store drops (and logs) any row without a unique_flight_key, so the aggregate receives a guaranteed-present, valid id.” Clean at the boundary so the domain never re-checks; its invariants are guaranteed by the fact that nothing invalid can reach it.
Try it yourself
Decide at the boundary
State Nick's rule in your own words.
Nick asked, of a nullable date threaded deep into a query store, "Why allow null at all? ... make this explicit upstream rather than buried downstream." What's the general rule, and how did Rai resolve it?
Reveal answer
A business default (what 'no date supplied' means) must be resolved at the boundary, the endpoint, not defaulted with ?? today deep in the persistence layer. Rai's fix: Execute now takes a non-null DateOnly, and the endpoint resolves the default before calling, so the operational day is decided at the edge rather than threaded down as a nullable. Low layers shouldn't make product decisions.
Where the retry belongs
Nisha wrote that browser-side retrying "should not have been a frontend concern in the first place ... it should have stayed in the backend." What layering principle is that?
Show answer
Correct answer: D — Retry belongs to the layer that owns the operation; pushing it to the client inverts the layering
Retry is a backend responsibility because the backend owns the call to the flaky dependency. Surfacing it to the browser inverts the layering and piles client retries onto a cold-start timeout, making it worse. Each layer keeps the concerns it owns.