Why does EK9 suggest promoting an injected type to a field when it appears in three or more methods (E11041)?

← Code Quality · Ref: Q1340

When the SAME injected type is requested via the method-local '!' suffix inside 3 or more methods of a single component, E11041 (INJECTION_PROMOTE_TO_FACADE) advises promoting that dependency to a component field. Repeating the local injection in every method is a smell: the dependency is clearly central to the component's purpose, so it belongs as a single declared field rather than being re-resolved on every call.

Fix: declare the dependency once as a field with '!' at component scope, then have each method use that field. If a component accumulates many such central dependencies, group the related collaborators behind one facade component and inject the facade instead.

See Q960 for the per-component injection-field limit (E11040). See Q311 for quality checks.

Example

defines module qa.quality.injection.promote

  defines component

    //Abstract contract for the central dependency.
    AuditService as abstract
      record() as abstract
        -> message as String
      default operator ?

    ConsoleAudit extends AuditService
      override record()
        -> message as String
        Stdout().println("[AUDIT] " + message)
      default operator ?

    //THE FIX: the AuditService is central to this component, so it is
    //declared ONCE as a field with '!' rather than re-injected locally
    //inside every method. Each method simply uses the field.
    OrderProcessor

      audit as AuditService!

      submit()
        -> label as String
        audit.record(`submit ${label}`)

      cancel()
        -> label as String
        audit.record(`cancel ${label}`)

      refund()
        -> label as String
        audit.record(`refund ${label}`)

      default operator ?

  defines application

    OrderApp
      register ConsoleAudit() as AuditService

  defines program

    //CORRECT: the central dependency is a single field, used by all methods.
    PromoteInjectionDemo() with application of OrderApp
      stdout <- Stdout()
      processor <- OrderProcessor()
      processor.submit("order-1")
      stdout.println("Central dependency promoted to a single field")

Common mistakes

E11041 — The same AuditService type is method-local injected ('!') inside three separate methods, signalling that it is central to the component. Promote it to a single component field declared once with '!', then have every method use that field. If many central dependencies pile up, group them behind one facade component. See ek9 -h E11041 for details.

Incorrect:

submit()
        -> label as String
        audit as AuditService!
        audit.record(label)

      cancel()
        -> label as String
        audit as AuditService!
        audit.record(label)

      refund()
        -> label as String
        audit as AuditService!
        audit.record(label)

Correct:

      audit as AuditService!

      submit()
        -> label as String
        audit.record(`submit ${label}`)
Other ways to ask this
  • What triggers E11041 INJECTION_PROMOTE_TO_FACADE?
  • Why does EK9 flag the same '!' injected type used in 3+ methods of one component?
  • How do I fix a component that re-injects the same service in every method?

Coming from another language?

Java Spring: @Autowired on a field is conventional, but nothing stops repeated method-parameter injection or ObjectProvider lookups in every method; detected only by SonarQube/ArchUnit if configured. C#/.NET: Seemann recommends constructor injection of central dependencies but it is advisory. Go Wire: no per-method guidance. EK9: the compiler observes the repetition of a single injected type across 3+ methods and steers you to promote it to a field (E11041).

Keywords: injection, field, promote, quality, E11041, dependency, method, component, facade