Skip to content

0005: Dependency injection by type through ServiceLets

Context and Problem Statement

Operations need services, services need settings, the kernel or other services. Global singletons make testing hard; a DI framework is heavy and unfamiliar.

Considered Options

  • Injection by type annotation through ServiceLets (chosen)
  • A DI library (dependency-injector, lagom): more concepts than we need.
  • Passing the kernel everywhere: hides real dependencies.

Decision Outcome

Chosen option: dependency injection by type through ServiceLets, because operations stay plain functions and tests can pass fakes.

  • A module advertises module.service(SomeService) (optionally with a factory).
  • kernel.call(func, **given) injects every parameter whose annotation is a provided type and wasn't given: Kernel, Settings, and all service types. Services are built lazily, once per kernel; their constructors are injected the same way.
  • Two modules providing the same type is an error at boot.

Consequences

  • Good: operations read like plain functions; tests pass fakes as keyword arguments or build a kernel with test modules.
  • Good: heads hide injected parameters from their interface automatically.
  • Bad: cycles between services surface as recursion errors at first use; keep service graphs shallow.

Confirmation

tests/unit/kernel/kernel_test.py: test_injects_kernel_settings_and_services_once, test_call_injects_only_missing_parameters, test_service_provided_twice_by_builtins_is_a_bug, test_service_provided_twice_by_a_plugin_is_reported.