skip to content

How would you govern ordering when many teams contribute interceptors to one shared chain around every request?

level: principalimportance: should knowfreq 30%

answer

  1. ordering is an interface between teams
  2. bands first, constraints within them
  3. an ambiguous pair should fail the build
  4. declare powers, not only position
  5. print and assert the resolved order

basics

~20 s

Publish ordering as a contract, not a setting: reserved bands for admission, observability and business handlers, relative constraints rather than hand-picked numbers, declared powers for stopping a call or rewriting values, and a resolved order that is printed and asserted in tests.

solid answer

~50 s

Ordering in a shared chain is an interface between teams, so it needs the treatment an interface gets. I would define a small number of named bands - admission and authentication outermost, observability just inside them, translation next, business handlers nearest the target - and let a contributor declare a band plus relative constraints rather than picking a number, so the platform resolves a total order and fails the build on a cycle or a genuine ambiguity. Separately from position, handlers should declare their powers: whether they may stop the call, rewrite arguments, replace the result, or convert a failure, because those powers silently disable policies nested inside them. Finally the resolved order must be an artifact - printed at startup, asserted in a test - so a change to it is reviewable rather than emergent.

code

json · 12 lines
json
{
  "handler": "request-timing",
  "band": "observability",
  "mustSitOutside": ["retry", "fallback"],
  "mustSitInside": ["authentication"],
  "powers": {
    "mayStopTheCall": false,
    "mayRewriteArguments": false,
    "mayReplaceResult": false,
    "mayProceedMoreThanOnce": false
  }
}

go deeper

for a junior

Recall that in a shared chain the order of handlers is decided centrally, because one team's handler changes what another team's handler sees.

for a middle

Explain why registration order is not a contract, and what a declared constraint records that a hand-picked number does not.

for a senior

Argue a concrete band layout and make the resolved order testable, so an ordering change shows up in a diff rather than in an incident.

for a principal

Own the trade between a small reviewed set of extension points and open contribution with a resolver, and govern powers - stopping, rewriting, repeating - alongside position.

## Ordering is an interface, not a configuration detail In a chain every team contributes to, position determines observable behaviour: which handler sees a value before it is rewritten, whose policy is silently skipped when an outer handler stops the call, whether a measurement includes the retries. Teams cannot reason about that locally, because the consequence of their handler's position depends on handlers they did not write. So the ordering has to be published and governed centrally, like any other interface between teams, rather than emerging from registration order or from whichever number someone picked last. ## Absolute ranks versus relative constraints The two common mechanisms trade differently: | | absolute numeric rank | declared relative constraints | |---|---|---| | how a contributor expresses intent | picks a number | says what it must sit outside or inside | | what happens as contributors multiply | numbers cluster and get renumbered | constraints compose; the platform resolves them | | is a conflict detectable | no, two equal ranks resolve arbitrarily | yes, a cycle or an underdetermined pair is an error | | what a reviewer reads | a number with no rationale | the requirement that justified the position | | failure mode | silent reordering after a merge | a build that refuses to resolve | Relative constraints scale better, but only if the resolver treats an underdetermined pair as an error rather than picking one arbitrarily - otherwise you have absolute ranks again with extra steps. In practice a hybrid works: coarse **bands** that are absolute and few, and relative constraints *within* a band. ## Bands worth reserving 1. **Admission** - authentication, authorisation, quota, load shedding. Outermost, because they decide whether anything inside runs at all, and because their refusals must be cheap. 2. **Observability** - the measurements and records that must cover everything inside, including retries and waits, and that must still run when the call was refused further in. 3. **Context and translation** - handlers that establish per-call context or convert values and failures into the vocabulary the inner handlers use. 4. **Business and resilience** - retrying, fallback, caching-style handlers, nearest the target, where their repeated inward calls and substituted values are contained. The bands are worth naming even when the exact membership is debatable, because a contributor arguing for a band is arguing about *semantics*, which a reviewer can adjudicate, rather than about a number, which nobody can. ## Position is not the only power Two handlers at the same position are not interchangeable. A contributed handler should declare, and be reviewed for: - whether it may **stop the call**, which disables every policy nested inside it for the calls it stops; - whether it **rewrites arguments** inward, which changes what every nested handler and the target see; - whether it **replaces the result** outward, which changes what every outer handler and the caller see; - whether it **converts a failure into a value**, which makes every outer handler report success; - whether it may **proceed more than once**, which multiplies the work of everything nested inside it. Each of those is a power that a reviewer should grant deliberately. A platform that governs position but not powers has governed the easy half. ## Making the order an artifact The ordering should be *visible* in three places: resolved and printed when the chain is assembled, asserted in a test that fails when the sequence changes, and documented with the reason each band exists. That converts an ordering change from something that happens when a dependency is added into something that appears in a diff and gets reviewed. A deliberately cheap escape hatch matters too - a documented way to run a handler outside the framework entirely - because the alternative is that a team with an unusual requirement quietly invents a second chain. ## What the governance costs Every handler adds a frame, some latency and some diagnostic noise to every request, so an open contribution model grows a chain nobody can reason about even with perfect ordering. The genuine judgment call is how open the chain should be: a small fixed set of extension points, reviewed individually, keeps the chain comprehensible at the cost of telling teams no; open contribution with a resolver scales to more teams and eventually produces a chain whose behaviour is emergent. Most platforms land between them, and the honest answer names the trade rather than claiming one side is correct.

  • Why is a hand-picked numeric rank a poor way to express ordering in a chain many teams extend?
    Because the number records a decision without its reason, and nothing detects a collision: two handlers given the same rank resolve arbitrarily, and the resolution can change when a dependency is added. A declared constraint records the requirement and lets the resolver reject a genuine ambiguity.
  • Which power is most dangerous to grant without review, and why?
    The power to stop a call, because it silently disables every policy nested inside that handler for the calls it stops. The disabled policies keep reporting on the traffic they still see, so the gap is invisible from their own metrics.
  • How would you keep the chain's length from growing without limit?
    Treat each handler as a cost paid by every request: require a named owner and a stated requirement, review additions against the extension points that already exist, and periodically retire handlers whose reason has lapsed. Publish the per-request overhead so the cost is a number, not an opinion.

saying these in an interview costs you the question

  • Treats ordering as a local configuration each team sets
  • Relies on registration order and calls it deterministic
  • Governs position while ignoring which handlers may stop a call
  • Resolves an ambiguous ordering arbitrarily instead of failing
  • Assumes a longer chain costs nothing per request