Why can't I register two implementations for the same abstract type?

← DI Validation · Ref: Q674

Each abstract component type can have exactly ONE concrete registration per application (E08230). Duplicate registrations create ambiguity in injection resolution.

WHY ONE REGISTRATION ONLY

When a program injects 'service as Logger!', the compiler must resolve exactly one concrete implementation. Two registrations for Logger would make resolution ambiguous.

DIFFERENT APPLICATIONS FOR DIFFERENT WIRING

Instead of duplicate registrations, use different applications:

  defines application
    ProdApp
      register FileLogger() as Logger
  defines application
    TestApp
      register MockLogger() as Logger

Each application has one Logger binding.

See Q672 for program-application linking. See Q673 for missing registration. See Q671 for circular dependencies.

Example

defines module qa.divalidation.duplicatereg

  defines component

    <?-
      Abstract email service contract.
    -?>
    EmailService as abstract

      sendEmail() as abstract
        -> recipient as String

      default operator ?

    <?-
      SMTP implementation for production.
    -?>
    SmtpEmailService extends EmailService

      override sendEmail()
        -> recipient as String
        stdout <- Stdout()
        stdout.println(`SMTP email to ${recipient}`)

      default operator ?

    <?-
      Mock implementation for testing.
    -?>
    MockEmailService extends EmailService

      override sendEmail()
        -> recipient as String
        stdout <- Stdout()
        stdout.println(`Mock email to ${recipient}`)

      default operator ?

  defines application

    <?-
      Production application: ONE registration for EmailService.
      Each abstract type has exactly one concrete binding.
    -?>
    ProductionConfig
      register SmtpEmailService() as EmailService

    <?-
      Test application: different implementation, same abstract type.
      Use separate applications for different wiring configurations.
    -?>
    TestConfig
      register MockEmailService() as EmailService

  defines program

    DuplicateRegistrationDemo() with application of ProductionConfig
      stdout <- Stdout()

      emailService as EmailService!
      emailService.sendEmail("user@example.com")

      stdout.println("One abstract type = one registration per application")
      stdout.println("Use different applications for different implementations")

Common mistakes

E50010 — Each abstract component type can have exactly one concrete registration per application. Registering two implementations for EmailService in the same application creates ambiguity when the compiler tries to resolve injection points. Use separate applications for different wiring configurations. See ek9 -h E50010 for details.

Incorrect:

BrokenConfig
      register SmtpEmailService() as EmailService
      register MockEmailService() as EmailService

Correct:

ProductionConfig
      register SmtpEmailService() as EmailService
Other ways to ask this
  • What is E08230 duplicate registration?
  • How do I handle multiple implementations of one interface?
  • Can I register the same abstract type twice?

Coming from another language?

Java: Spring requires @Primary or @Qualifier for multiple beans. Python: manual selection. Go: manual wiring. Rust: no DI framework. EK9: one registration per abstract type per application, use different applications for different configurations.

Keywords: application, duplicate, registration, inject, DI, component, validate, E08230, ambiguous