How does EK9 validate DI component registration and dependency ordering?

← DI Validation · Ref: Q691

EK9 validates the complete dependency injection graph at compile time. All components must be registered in the application, and the compiler checks that all dependencies can be satisfied.

REGISTRATION REQUIREMENT (E08210)

Every abstract component that is injected must have a concrete implementation registered in the application. A missing registration triggers E08210 ('... which is never registered in this application').

DEPENDENCY CHAIN

If ComponentA depends on ComponentB, and ComponentB depends on ComponentC, all three must be registered. The compiler walks the entire dependency chain.

REGISTRATION ORDER (E08200)

Registrations are made in the order they are written, so each dependency must be registered BEFORE the component that injects it. Registering a dependent first triggers E08200 ('... which is not yet registered, reorder registrations'):

  register OrderCoordinator() as Coordinator        // E08200: needs OrderService, not yet registered
  register DefaultOrderService() as OrderService    // E08200: needs Repository, not yet registered
  register InMemoryRepository() as Repository

Fix: register bottom-up - Repository, then OrderService, then Coordinator - as the example below does.

ABSTRACT INJECTION RULE

DI only works with abstract components:

  defines component
    DataAccess as abstract
      load() as abstract
        <- rtn as String?
      default operator ?
    DatabaseAccess extends DataAccess
      override load()
        <- rtn as String: "row"
      default operator ?

DataAccess can be injected; DatabaseAccess is the concrete implementation registered for it.
Concrete components cannot be directly injected.

APPLICATION WIRING

The application block registers concrete implementations:

  defines application
    MyApp
      register DatabaseAccess() as DataAccess
      register OrderLogic() as BusinessLogic

See Q675 for transitive DI validation. See Q673 for missing registration. See Q674 for duplicate registration.

Example

defines module qa.divalidation.registrationorder

  defines component

    <?-
      Layer 1: Abstract repository.
      Bottom of the dependency chain.
    -?>
    Repository as abstract

      findByKey() as abstract
        -> lookupKey as String
        <- record as String?

      default operator ?

    <?-
      Concrete repository implementation.
    -?>
    InMemoryRepository extends Repository

      override findByKey()
        -> lookupKey as String
        <- record as String: ""

        record: "record:" + lookupKey

      default operator ?

    <?-
      Layer 2: Abstract service.
      Depends on Repository (injected).
    -?>
    OrderService as abstract

      processOrder() as abstract
        -> orderId as String
        <- confirmation as String?

      default operator ?

    <?-
      Concrete service implementation.
      Injects Repository (must be registered).
    -?>
    DefaultOrderService extends OrderService

      repository as Repository!

      override processOrder()
        -> orderId as String
        <- confirmation as String: ""

        if lookupResult <- repository.findByKey(orderId)
          confirmation: "Confirmed: " + lookupResult

      default operator ?

    <?-
      Layer 3: Abstract coordinator.
      Depends on OrderService (injected).
    -?>
    Coordinator as abstract

      coordinate() as abstract
        -> taskId as String
        <- outcome as String?

      default operator ?

    <?-
      Concrete coordinator implementation.
      Injects OrderService (transitive chain).
    -?>
    OrderCoordinator extends Coordinator

      orderService as OrderService!

      override coordinate()
        -> taskId as String
        <- outcome as String: ""

        if result <- orderService.processOrder(taskId)
          outcome: "Coordinated: " + result

      default operator ?

  defines application

    <?-
      Application must register ALL levels of the dependency chain (a missing one is E08210),
      each dependency before the component that injects it (out of order is E08200).
    -?>
    OrderApp
      register InMemoryRepository() as Repository
      register DefaultOrderService() as OrderService
      register OrderCoordinator() as Coordinator

  defines program

    DiRegistrationOrderDemo() with application of OrderApp
      stdout <- Stdout()

      coordinator as Coordinator!
      if outcome <- coordinator.coordinate("ORD-001")
        stdout.println(outcome)

      stdout.println("All three layers registered and validated at compile time")

Common mistakes

E08210 — The compiler validates that all components in the dependency chain have registrations. OrderCoordinator depends on OrderService, which depends on Repository. All three must be registered in the application; a missing registration in the chain is E08210. See ek9 -h E08210 for details.

Incorrect:

OrderApp
      register OrderCoordinator() as Coordinator

Correct:

OrderApp
      register InMemoryRepository() as Repository
      register DefaultOrderService() as OrderService
      register OrderCoordinator() as Coordinator

E08200 — Each dependency must be registered before the component that injects it. Registering OrderCoordinator before OrderService (and DefaultOrderService before Repository) is E08200: reorder the registrations bottom-up. See ek9 -h E08200 for details.

Incorrect:

      register OrderCoordinator() as Coordinator
      register DefaultOrderService() as OrderService
      register InMemoryRepository() as Repository

Correct:

      register InMemoryRepository() as Repository
      register DefaultOrderService() as OrderService
      register OrderCoordinator() as Coordinator
Other ways to ask this
  • What is E08200 DI registration order error?
  • Does EK9 validate dependency injection at compile time?
  • How must components be registered in an application?
  • What happens if a dependency is registered after its dependent?

Coming from another language?

Java: Spring validates at startup (runtime). Python: runtime discovery. Go: manual wiring (compile-time). Rust: no DI framework. Kotlin: Koin validates at runtime. EK9: full compile-time DI validation of the complete dependency graph.

Keywords: injection, dependency, validate, component, order, registration, E08200, E08210, compile-time, application, inject, sanitize, DI