E08190: circular dependency detected in component injection chain

Category: E08xxx Pre-IR checks (flow analysis) · Reported in phase: PRE_IR_CHECKS · Classification: DI_CIRCULAR_DEPENDENCY · All error codes

What went wrong

A circular dependency has been detected in the component injection chain. Component A requires Component B, which requires Component A (directly or transitively). Circular dependencies make object creation impossible.

Each tangle of components is reported once, at the member registered first, with one cycle through it. No order of registrations could satisfy a cycle, so its members are not also reported as used before registration (E08200). The failing source in the [EXAMPLE] section below (bad.ek9) has OrderService and BillingService each injecting the other.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 671 (How does EK9 prevent circular dependencies in DI?)
  • ek9 -q 797 (How does EK9 detect circular component injection at compi...)

How to fix it

  1. Map out the dependency chain to find the cycle
  2. Extract shared logic into a new component
  3. Use an abstract trait to break the direct dependency
  4. Consider if the circular dependency indicates a design problem
  5. Then register the component that needs nothing first (good.ek9 below: BillingService no longer injects Orders, so it is registered before OrderService)

See also

  • E08190: circular dependency (this error)
  • E08200: ordering error in application (not reported for a cycle's members)
  • E08210: missing registration

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E08190: circular dependency detected in component injection chain

#!ek9
defines module error.e08190.bad

  defines component

    Orders as abstract
      place() as abstract

    Billing as abstract
      charge() as abstract

    OrderService is Orders
      billing as Billing!
      override place()
        billing.charge()
      default operator ?

    BillingService is Billing
      orders as Orders!
      override charge()
        orders.place()
      default operator ?

  defines application

    shopApp
      register OrderService() as Orders
      register BillingService() as Billing

  defines program

    Shop() with application of shopApp
      orders as Orders!
      orders.place()

Fixed: Break the cycle: Billing needs nothing from Orders, so it is registered first

#!ek9
defines module error.e08190.good

  defines component

    Orders as abstract
      place() as abstract

    Billing as abstract
      charge() as abstract

    OrderService is Orders
      billing as Billing!
      override place()
        billing.charge()
      default operator ?

    BillingService is Billing
      override charge()
        Stdout().println("charged")

  defines application

    shopApp
      register BillingService() as Billing
      register OrderService() as Orders

  defines program

    Shop() with application of shopApp
      orders as Orders!
      orders.place()

Questions and answers

These Q&A entries show E08190 and its fix: