Why does registration order matter in EK9 applications?

← Dependency Injection · Ref: Q228

In EK9, the order of 'register' statements in an application definition matters. Dependencies must be registered BEFORE the components that depend on them.

WHY ORDER MATTERS

EK9 processes registrations sequentially. When the compiler encounters a component with injection fields, it verifies those dependencies are already registered. If component B injects component A, then A must appear before B in the register list.

CORRECT ORDERING

For a chain A -> B -> C (C depends on B, B depends on A):

  register ConcreteA() as AbstractA    <- no dependencies, register first
  register ConcreteB() as AbstractB    <- depends on A, register second
  register ConcreteC() as AbstractC    <- depends on B, register third

WHAT HAPPENS WITH WRONG ORDER

If you register B before A, the compiler reports an error because B's injection field for A cannot be satisfied at that point in the registration sequence.

DESIGN RATIONALE

Explicit ordering makes the dependency graph visible in the application definition. You can read the register list top-to-bottom and understand the initialization order. No hidden runtime sorting or lazy resolution.

See Q111 for component basics. See Q227 for compile-time validation overview. See Q229 for circular dependency detection. See Q232 for transitive component chains.

See Q326 for @Bean/@Configuration equivalent.

Example

defines module qa.di.ordering

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- output as String?

      default operator ?

    ConsoleLogger is Logger
      override log()
        -> message as String
        <- output as String: "[LOG] " + message

      default operator ?

    Formatter as abstract
      format() as abstract
        -> text as String
        <- result as String?

      default operator ?

    SimpleFormatter is Formatter
      logger as Logger!

      override format()
        -> text as String
        <- result <- String()
        result: logger.log(text.upperCase())

      default operator ?

    NotificationService as abstract
      notify() as abstract
        -> message as String
        <- result as String?

      default operator ?

    EmailNotifier is NotificationService
      formatter as Formatter!

      override notify()
        -> message as String
        <- result <- String()
        result: formatter.format("NOTIFY: " + message)

      default operator ?

  defines application

    OrderedApp
      // Dependencies registered BEFORE dependents
      register ConsoleLogger() as Logger
      register SimpleFormatter() as Formatter
      register EmailNotifier() as NotificationService

  defines program

    OrderingDemo() with application of OrderedApp
      stdout <- Stdout()

      // === CORRECT ORDER: Logger -> Formatter -> NotificationService ===

      notifier as NotificationService!

      result <- notifier.notify("System ready")
      stdout.println(result)

      stdout.println("Three-layer chain wired in correct order")

Common mistakes

E08200 — SimpleFormatter injects Logger, so Logger must be registered before SimpleFormatter. Reversing the order means the dependency is not yet available when the compiler processes SimpleFormatter. See ek9 -h E08200 for details.

Incorrect:

register SimpleFormatter() as Formatter
      register ConsoleLogger() as Logger

Correct:

register ConsoleLogger() as Logger
      register SimpleFormatter() as Formatter

E08150 — Injection fields must reference abstract component types, not concrete implementations. The application registers the concrete type; the field declares the abstract type. See ek9 -h E08150 for details.

Incorrect:

logger as ConsoleLogger!

Correct:

logger as Logger!
Other ways to ask this
  • What order should I register components in EK9?
  • Does the sequence of register statements matter in EK9?
  • How do I order dependencies in an EK9 application?

Coming from another language?

Java Spring: registration order does not matter, container resolves lazily. Guice: binding order irrelevant, dependency graph resolved at injector creation. .NET: registration order irrelevant, resolved on demand. Python: manual wiring, order depends on programmer. Go: manual wiring, initialization order explicit. Rust: no built-in DI. EK9: explicit registration order required, dependencies before dependents, compiler enforces correct sequencing.

Keywords: order, explicit, dependency, register, before, application, chain, initialization, inject, after, sequence