Skip to content

Dependency Handle Registry

Resolution registry — instantiates Definition-Time Context Binding

Binds each dependency a unit needs to a stable handle, so its references resolve to the same identity across contexts instead of re-resolving against whatever is ambient.

A Dependency Handle Registry is a lookup table that stands between a behavior unit and the things it depends on. Instead of a unit reaching out to a global by name — and getting whatever that name happens to mean in the environment it lands in — the unit references a stable handle, and the registry resolves that handle to a definite provider under an explicit precedence rule. Its defining move is reference identity: the registry guarantees that "the clock," "the payment gateway," or "the tenant config" a unit asked for resolves to the same intended thing wherever the unit runs, rather than silently re-resolving against an ambient namespace. It is the indirection layer that makes a name mean one thing on purpose.

Example

An order-processing service is written once but runs in three places: production, a staging environment, and each developer's laptop. Its code needs "the payment gateway," "the email sender," and "the current-time source." Reference those as ambient globals and the same code quietly binds to the live gateway on a laptop (charging real cards during a test) or to a real clock in a test that needed a frozen one. A dependency handle registry fixes each need to a handle — payments, mailer, clock — and resolves each handle to a provider chosen by precedence: a test registers a fake payments, which shadows the default; production registers the real one. The unit's references never change; what they resolve to is governed centrally. The developer's run binds payments to a sandbox and clock to a fixed time, and the code that "just uses the payment gateway" does the safe thing without being rewritten.

How it works

  • Register providers under handles. Each dependency is a named handle bound to a concrete provider, so units name what they need, not where it lives.
  • Resolve by precedence. When a handle could bind to more than one provider, an explicit precedence rule decides the winner (test override beats default beats fallback), rather than leaving it to load order or ambient chance.
  • Adapt the ambient. Where a unit would otherwise reach for an ambient global, the registry interposes — bridging the concrete environment to the declared handle so the unit resolves through the registry, not around it.

Tuning parameters

  • Resolution scope — one global registry, or nested/scoped registries (per request, per tenant, per test). Scoping enables local overrides but multiplies the places a handle can resolve.
  • Binding lifetime — singleton (resolve once, share), or per-resolution (fresh each time). Singletons are efficient but share state; per-resolution isolates but costs.
  • Override precedence — how aggressively a more-specific registration shadows a general one. Strong override eases testing and multi-tenancy; too much makes resolution hard to predict.
  • Strictness on miss — fail loudly on an unregistered handle, or fall back to ambient. Strict catches missing wiring early; lenient is forgiving but reopens the ambient-coupling door.

When it helps, and when it misleads

Its strength is that it makes dependency resolution explicit and identity-stable: the same unit runs faithfully across environments because what its handles resolve to is decided in one governed place, not inherited by accident. The honest failure mode is that the registry can become the very thing it replaced — a global service locator that units reach into implicitly, turning it into a hidden ambient dependency of its own and obscuring what a unit actually needs.[1] The classic misuse is a lenient, sprawling global registry with silent ambient fallback, which quietly rebuilds the coupling it was meant to sever. The discipline is to scope registries narrowly, prefer passing what a unit needs over letting it fish, fail loud on missing handles, and keep the precedence rules few and legible.

How it implements the components

  • reference_identity_rule — it guarantees each handle resolves to the same intended provider identity across contexts, rather than re-resolving by ambient name.
  • resolution_precedence_rule — it applies an explicit rule for which provider wins when a handle could bind to several.
  • ambient_context_adapter — it interposes on ambient lookups, bridging the concrete environment to declared handles so resolution goes through the registry.

It resolves references by handle but does not itself reify and pass the whole environment as one explicit value — that is Explicit Environment Object — nor does it perform the original definition-site capture, which is Lexical Closure.

References

[1] The service-locator vs. dependency-injection distinction (Martin Fowler): a service locator lets a unit ask a registry for its dependencies, which is flexible but hides those dependencies inside the code; injection passes them in, making needs explicit. A dependency handle registry is a service locator, and inherits both its convenience and its main hazard.