Why does EK9 limit injection fields per component?

← Dependency Injection · Ref: Q862

Components with more than 4 injection fields (marked with '!') violate the Single Responsibility Principle. The compiler raises E11040 to enforce focused components.

WHY THE LIMIT EXISTS

A component with 5+ injected dependencies is doing too much. Each dependency represents a concern the component must manage. More than 4 suggests the component should be split.

THE FIX

Extract a facade component to group related dependencies:
1. Identify clusters of related injected services
2. Create a facade component that wraps the cluster
3. Inject the facade instead of individual services

THIS EXAMPLE

Shows a component with exactly 3 injection fields, well within the limit. Each dependency is clearly needed for the component's focused responsibility.

See Q227 for compile-time DI validation. See Q229 for circular deps. See Q325 for component patterns.

Example

defines module qa.di.injection.field.limit

  defines component

    <?-
      Abstract service contracts for injection.
    -?>
    Repository as abstract
      findById() as abstract
        -> identifier as String
        <- rtn as String?
      default operator ?

    Notifier as abstract
      notify() as abstract
        -> message as String
      default operator ?

    Auditor as abstract
      record() as abstract
        -> action as String
      default operator ?

    <?-
      Concrete implementations.
    -?>
    InMemoryRepository extends Repository
      override findById()
        -> identifier as String
        <- rtn as String: "item-" + identifier
      default operator ?

    ConsoleNotifier extends Notifier
      override notify()
        -> message as String
        Stdout().println("[NOTIFY] " + message)
      default operator ?

    ConsoleAuditor extends Auditor
      override record()
        -> action as String
        Stdout().println("[AUDIT] " + action)
      default operator ?

    <?-
      Correct: 3 injection fields, within the limit of 4.
      Each dependency is clearly needed for order processing.
    -?>
    OrderProcessor

      repo as Repository!
      notifier as Notifier!
      auditor as Auditor!

      processOrder()
        -> orderId as String
        item <- repo.findById(orderId)
        if item?
          notifier.notify(`Processing ${item}`)
          auditor.record(`Processed order ${orderId}`)

      default operator ?

  defines application

    OrderApp
      register InMemoryRepository() as Repository
      register ConsoleNotifier() as Notifier
      register ConsoleAuditor() as Auditor

  defines program

    InjectionLimitDemo()
      stdout <- Stdout()
      stdout.println("OrderProcessor has 3 injection fields (within limit of 4)")
      stdout.println("Components with 5+ injection fields trigger E11040")

Common mistakes

E11040 — 5 injection fields exceeds the limit of 4. Extract related services into a facade component and inject the facade instead. See ek9 -h E11040 for details.

Incorrect:

      repo as Repository!
      notifier as Notifier!
      auditor as Auditor!
      extra1 as Repository!
      extra2 as Notifier!

Correct:

      repo as Repository!
      notifier as Notifier!
      auditor as Auditor!
Other ways to ask this
  • What is E11040 EXCESSIVE_INJECTION_FIELDS in EK9?
  • How many injection fields can a component have?
  • How do I fix too many injected dependencies?

Coming from another language?

Java: Spring has no limit on @Autowired fields (only advisory from SonarQube). Python: no limit. Kotlin: no limit. Go: no DI framework limits. Rust: no DI concept. EK9: E11040 compile-time limit of 4 injection fields per component.

Keywords: limit, injection, dependency, component, E11040, field, SRP, inject, clean-code, facade, quality