What is the lifecycle of injected components in EK9?
← Dependency Injection · Ref: Q234
EK9 components follow a simple three-phase lifecycle: prepare, execute, decommission. There is only one scope: singleton for the program's lifetime.
PREPARE PHASE
When a program starts, the application's _prepare() method creates all registered components in registration order. Dependencies are created before dependents, so injection fields are satisfied as each component is initialized.
EXECUTE PHASE
The program body runs with all components fully initialized. Every injection point references the same instance throughout the program's execution.
DECOMMISSION PHASE
When the program completes, _decommission() releases components in REVERSE registration order. Dependents are released before their dependencies, ensuring no component uses a decommissioned dependency.
SINGLETON ONLY
EK9 has one scope: singleton for the program lifetime. There are no request, session, or prototype scopes. This simplicity eliminates an entire class of scope-related bugs.
CONTRAST WITH SPRING
Spring has singleton, prototype, request, session, and application scopes. Each scope adds complexity and potential for scope mismatch bugs (e.g., injecting a request-scoped bean into a singleton). EK9 avoids this entirely.
WHY SINGLETON ONLY
Most DI complexity comes from managing multiple scopes. Programs in EK9 are designed to be focused units. If you need different lifecycle management, use explicit construction within the program body.
See Q117 for singleton lifecycle. See Q111 for component basics. See Q227 for compile-time validation. See Q231 for program-application linking.
See Q330 for singleton-only scope rationale. See Q332 for performance comparison.
Example
defines module qa.di.lifecycle defines component Logger as abstract log() as abstract -> message as String <- output as String? default operator ? AppLogger is Logger override log() -> message as String <- output as String: `[${message}]` default operator ? DataStore as abstract save() as abstract -> content as String <- result as String? default operator ? InMemoryStore is DataStore logger as Logger! override save() -> content as String <- result <- String() result: logger.log("Saved: " + content) default operator ? AppService as abstract run() as abstract -> input as String <- output as String? default operator ? MainService is AppService store as DataStore! logger as Logger! override run() -> input as String <- output <- String() saved <- store.save(input) output: logger.log("Service processed: " + saved) default operator ? defines application LifecycleApp // _prepare() creates in this order: // 1. AppLogger (no dependencies) // 2. InMemoryStore (depends on Logger) // 3. MainService (depends on DataStore and Logger) register AppLogger() as Logger register InMemoryStore() as DataStore register MainService() as AppService // _decommission() releases in reverse: // 1. MainService // 2. InMemoryStore // 3. AppLogger defines program LifecycleDemo() with application of LifecycleApp stdout <- Stdout() // === EXECUTE PHASE: all components fully initialized === service as AppService! result <- service.run("test data") stdout.println(result) stdout.println("Components created in order, released in reverse")
Common mistakes
E08200 — MainService depends on both DataStore and Logger, so both must be registered before MainService. The prepare phase creates components in registration order, so dependencies must come first. See ek9 -h E08200 for details.
Incorrect:
register MainService() as AppService register AppLogger() as Logger register InMemoryStore() as DataStore
Correct:
register AppLogger() as Logger register InMemoryStore() as DataStore register MainService() as AppService
E08150 — Injection fields must use abstract component types. The lifecycle manager resolves abstract types to their registered concrete implementations during the prepare phase. See ek9 -h E08150 for details.
Incorrect:
store as InMemoryStore!
Correct:
store as DataStore!
Other ways to ask this
- How are EK9 components created and destroyed?
- What are the DI lifecycle phases in EK9?
- Does EK9 support different component scopes like Spring?
Coming from another language?
Java Spring: singleton (default), prototype, request, session, application scopes. Complex lifecycle: @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, BeanPostProcessor. Guice: unscoped (default), @Singleton, @RequestScoped, custom scopes. .NET: transient, scoped, singleton lifetimes. Python: manual lifecycle management. Go: manual lifecycle. Rust: ownership-based lifecycle. EK9: singleton only, _prepare() creates in order, _decommission() releases in reverse, no scope mismatch bugs.
Keywords: create, scope, phase, lifecycle, dependency, prepare, singleton, order, migrate, inject, component, decommission, reverse, destroy