Why must injected components be abstract types?
← DI Validation · Ref: Q667
EK9 requires injected component types to be abstract (E08150). This enforces the Dependency Inversion Principle: depend on abstractions, not implementations.
WHY ABSTRACT ONLY
Concrete injection creates tight coupling. Abstract injection allows:
1. Different applications wire different implementations 2. Test applications inject mocks 3. Production applications inject real services
REGISTRATION MUST USE 'AS'
Concrete components must be registered with 'as AbstractType':
register ConsoleLogger() as Logger // Correct register ConsoleLogger() // ERROR: E50020
Without 'as', the registration genus is incompatible.
INJECTION MUST USE ABSTRACT TYPE
logger as Logger! // Correct: abstract
Injecting the concrete type instead is wrong and fails with E08150:
defines component Logger as abstract log() as abstract -> message as String ConsoleLogger extends Logger override log() -> message as String stdout <- Stdout() stdout.println(message)
defines application ProdApp register ConsoleLogger() as Logger
defines program MyProgram with application of ProdApp logger as ConsoleLogger! // ERROR: E08150 logger.log("hello")
PATTERN
defines component Logger as abstract log() as abstract -> message as String ConsoleLogger extends Logger override log() -> message as String stdout <- Stdout() stdout.println(message)
defines application ProdApp register ConsoleLogger() as Logger
defines program MyProgram with application of ProdApp logger as Logger! // Inject abstract
See Q111 for component basics. See Q666 for pure context. See Q668 for injectable contexts. See Q324 for Autowired equivalent. See Q325 for Component equivalent.
Example
#!ek9 defines module qa.divalidation.abstractonly defines component <?- Abstract logger contract. Concrete implementations are registered in applications. -?> Logger as abstract logMessage() as abstract -> logEntry as String default operator ? <?- Console logger implementation. -?> ConsoleLogger extends Logger override logMessage() -> logEntry as String stdout <- Stdout() stdout.println(logEntry) default operator ? <?- Abstract repository contract. -?> UserRepository as abstract findByName() as abstract -> userName as String <- result as String? default operator ? <?- In-memory repository implementation. -?> InMemoryUserRepo extends UserRepository override findByName() -> userName as String <- result as String: "" result: "Found: " + userName default operator ? defines application ProductionApp register ConsoleLogger() as Logger register InMemoryUserRepo() as UserRepository defines program AbstractInjectionDemo() with application of ProductionApp stdout <- Stdout() logger as Logger! userRepo as UserRepository! logger.logMessage("Starting application") foundUser <- userRepo.findByName("Alice") stdout.println(foundUser) logger.logMessage("Application complete")
Common mistakes
E08150 — Only abstract component types can be injected. Injecting a concrete type like ConsoleLogger creates tight coupling and defeats the purpose of dependency injection. Depend on the abstract type Logger and let the application wiring choose the implementation. See ek9 -h E08150 for details.
Incorrect:
logger as ConsoleLogger!
Correct:
logger as Logger!
Other ways to ask this
- What is E08150 injecting concrete component?
- Why can't I inject a concrete component class?
- How does abstract injection enable testing?
Coming from another language?
Java: Spring injects concrete types (bad practice). Python: duck typing, no enforcement. Go: interfaces encouraged but not required. Rust: trait objects for abstraction. EK9: compiler enforces abstract injection, concrete types cannot be injected.
Keywords: DI, E08150, abstract, component, concrete, inject, injection, interface, registration, validate