Why does EK9 limit injection fields per component to four?

← Dependency Injection · Ref: Q960

A component with more than 4 injection fields (marked with '!') triggers E11040. This enforces the Single Responsibility Principle based on Clean Architecture research.

WHY FOUR IS THE LIMIT

Research from Robert C. Martin (Clean Architecture), Mark Seemann (Dependency Injection in .NET), and Shatnawi et al. (IEEE 2010) demonstrates that components with 5+ dependencies are typically doing too much.

HOW TO FIX

Group related dependencies into a facade component:
- Identify clusters of services used together
- Create a facade that wraps the cluster
- Inject the facade instead of individual services

THIS EXAMPLE

The TransactionCoordinator component has 4 injection fields, exactly at the limit. This compiles because 4 is the maximum allowed.

The typicalError mutation adds a fifth injection field, triggering E11040.

See Q862 for injection field limit overview. See Q227 for compile-time DI validation.

Example

defines module qa.di.repeatedinjection

  defines component

    <?-
      Abstract service contracts for transaction processing.
    -?>
    LedgerService as abstract
      recordEntry() as abstract
        -> description as String
      default operator ?

    AuthorizationService as abstract
      authorize() as abstract
        -> principal as String
        <- allowed as Boolean?
      default operator ?

    MonitoringService as abstract
      logActivity() as abstract
        -> activity as String
      default operator ?

    DispatchService as abstract
      sendResult() as abstract
        -> recipient as String
      default operator ?

    ArchivingService as abstract
      archiveRecord() as abstract
        -> recordId as String
      default operator ?

    <?-
      Concrete implementations.
    -?>
    InMemoryLedger extends LedgerService
      override recordEntry()
        -> description as String
        Stdout().println("[LEDGER] " + description)
      default operator ?

    SimpleAuthorizer extends AuthorizationService
      override authorize()
        -> principal as String
        <- allowed as Boolean: true
      default operator ?

    ConsoleMonitor extends MonitoringService
      override logActivity()
        -> activity as String
        Stdout().println("[MONITOR] " + activity)
      default operator ?

    LocalDispatcher extends DispatchService
      override sendResult()
        -> recipient as String
        Stdout().println("[DISPATCH] " + recipient)
      default operator ?

    FileArchiver extends ArchivingService
      override archiveRecord()
        -> recordId as String
        Stdout().println("[ARCHIVE] " + recordId)
      default operator ?

    <?-
      TransactionCoordinator has exactly 4 injection fields.
      This is at the limit but valid. Adding a 5th triggers E11040.
    -?>
    TransactionCoordinator

      ledger as LedgerService!
      authorizer as AuthorizationService!
      monitor as MonitoringService!
      dispatcher as DispatchService!

      processTransaction()
        -> txnLabel as String
        permitted <- authorizer.authorize(txnLabel)
        if permitted
          ledger.recordEntry(txnLabel)
          monitor.logActivity(txnLabel)
          dispatcher.sendResult(txnLabel)

      default operator ?

  defines application

    TransactionApp
      register InMemoryLedger() as LedgerService
      register SimpleAuthorizer() as AuthorizationService
      register ConsoleMonitor() as MonitoringService
      register LocalDispatcher() as DispatchService

  defines program

    RepeatedInjectionDemo()
      stdout <- Stdout()
      stdout.println("TransactionCoordinator has 4 injection fields (at limit)")
      stdout.println("Adding a 5th would 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:

      ledger as LedgerService!
      authorizer as AuthorizationService!
      monitor as MonitoringService!
      dispatcher as DispatchService!
      archiver as ArchivingService!

Correct:

      ledger as LedgerService!
      authorizer as AuthorizationService!
      monitor as MonitoringService!
      dispatcher as DispatchService!
Other ways to ask this
  • What triggers E11040 EXCESSIVE_INJECTION_FIELDS?
  • How many injected dependencies can a component have?
  • How do I refactor a component with too many injections?

Coming from another language?

Java Spring: unlimited @Autowired fields. C#: unlimited DI constructor parameters. Python: no DI framework limits. Go Wire: no limits. EK9: compile-time limit of 4 injection fields per component, enforced by E11040.

Keywords: field, component, SRP, quality, limit, excessive, dependency, injection, E11040, facade