Use a factory that resolves the correct implementation from a lookup map, then let dependency injection build the full object graph for that implementation. This keeps selection logic separate from business logic, supports open extension, and avoids hard coding type checks throughout the application. A builder can register new implementations cleanly while preserving the existing factory contract.
Why the factory should separate selection from construction
A runtime-selectable service factory works best when it treats “which implementation?” as a routing problem and “how do I build it?” as a composition problem. That split keeps the factory contract stable while allowing each implementation to have its own dependencies, configuration, and object graph without scattering conditionals across the codebase.
The practical value is that the calling code depends on one abstraction, while the factory resolves a concrete implementation from a lookup map and hands construction off to dependency injection. This is a cleaner extension model than adding more if-else branches, because new implementations can be registered without forcing edits to every caller or widening the selection logic.
That design also makes the factory easier to test. You can verify selection behavior with a small mapping of keys to implementations, then test each implementation in isolation through the injection container. For teams building plugin-style systems, feature variants, or tenant-specific behavior, this is usually the most maintainable path.
How to structure the lookup and registration model
The lookup map should be the only place where runtime selection happens. A string, enum, capability flag, or configuration key resolves to a factory entry, and that entry points to the implementation class or provider the container should create. The important constraint is that the factory should not need to know the internals of the implementation to make the decision.
Builders are useful when registration itself needs to be explicit, ordered, or environment-aware. A builder can assemble the registry of supported implementations, validate that required dependencies exist, and preserve the same factory API even as the implementation set grows. That is especially helpful when the set of options changes by deployment context rather than by business rule.
- Keep the selection key stable and domain-relevant.
- Register implementations centrally rather than inline at the call site.
- Let the container resolve constructor dependencies for the selected type.
- Fail fast if the requested key is unknown, missing, or ambiguous.
When the selection surface becomes more complex, the registry should still remain declarative. The goal is for the factory to answer “what type should I return?” and for the container to answer “how do I construct it correctly?” That division prevents the factory from becoming a hidden service locator with business logic baked into it.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
CIS Controls v8 and NIST CSF 2.0 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| CIS Controls v8 | CIS Control 16 — Application Software Security | Factory extensibility and DI wiring affect how application components are composed and controlled. |
| Recommendation — Apply secure design review to keep factory registration and component wiring maintainable. | ||
| NIST CSF 2.0 | PR.IP — Information Protection Processes and Procedures | Runtime selection and registration benefit from documented, repeatable engineering procedures. |
| Recommendation — Document the factory registration pattern so new implementations can be added consistently. | ||
Practitioner Guidance
What to verify: Make sure the runtime key is the only thing driving selection, and that the factory never inspects concrete types or reaches into implementation details to decide what to create. If a new implementation requires changing existing selection code, the abstraction is already too coupled.
Implementation sequence: Define the interface first, register each implementation in the lookup map, then let dependency injection materialize the chosen object graph. If you need per-environment or per-tenant variation, keep that variation in the registry or configuration layer, not in downstream business services.
Common mistake: Teams often start with a simple factory and then add type checks, manual construction, or partial wiring when implementations diverge. That usually signals that the factory is doing composition work that belongs in the container, or that the interface boundary is too broad.
Practitioner takeaway: The design should make runtime choice easy and object construction invisible to callers. If the factory still needs to know how each implementation is wired, it is no longer a clean factory, it is an orchestration bottleneck.
Related resources from NHI Mgmt Group
- How should security teams design self-service identity workflows without creating standing privilege?
- How should security teams add authorization to legacy applications without changing code?
- How should security teams design self-service access requests without losing accountability?
- How should AppSec teams implement ASPM when code, build, and runtime findings are spread across multiple pipelines?
Deepen Your Knowledge
Reviewed and updated by the NHIMG editorial team on September 18, 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org