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 logger as ConsoleLogger! // ERROR: E08150
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
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: interface, component, E08150, inject, DI, registration, injection, concrete, validate, abstract