Dependency injection
Register services, inject their dependencies, and resolve them through the request scope.
Dependency injection lets a service receive the other services it needs from the container. Lily generates service registrations through derive; the container takes responsibility for object creation and cleanup.
Dependencies#
The lilyrs dependency from the first application is sufficient:
lilyrs = { version = "0.1.0", features = ["http-api"] }Import Injectable, ServiceTrait, InjectionError, and other DI types from lilyrs::http_api. The derive recognizes the facade path; you do not need separate lily_injectable_derive, lily_injection_registry, lily_error, or linkme dependencies. The link-time registry remains part of the architecture; you do not write registrations or factories manually.
For a service library independent of the HTTP host, select features = ["injection"] and use lilyrs::injection::{Injectable, ServiceTrait}. http-api alone does not expose the lilyrs::injection module; the framework root shown above is sufficient for an HTTP application.
Define and use a service#
GreetingPolicy is shared throughout the application. GreetingService belongs to each request's scope and injects the policy. The controller receives the Scoped service as an action parameter.
use std::sync::Arc;
use lilyrs::http_api::Injectable;
use lilyrs::http_api::{
controller, Controller, ControllerInitError, ControllerTrait, Extensions,
HttpApiError, Service, ServiceTrait,
};
#[derive(Injectable)]
#[service(lifetime = "Singleton")]
struct GreetingPolicy;
impl ServiceTrait for GreetingPolicy {}
#[derive(Injectable)]
#[service(lifetime = "Scoped")]
struct GreetingService {
#[inject]
policy: Arc<GreetingPolicy>,
}
impl ServiceTrait for GreetingService {}
impl GreetingService {
fn message(&self) -> String { "Merhaba".to_owned() }
}
#[derive(Controller)]
#[base_path("/greetings")]
struct GreetingController { policy: Arc<GreetingPolicy> }
#[lilyrs::http_api::async_trait::async_trait]
impl ControllerTrait for GreetingController {
async fn new(extensions: Arc<Extensions>) -> Result<Self, ControllerInitError> {
let policy = extensions.get_service::<GreetingPolicy>(None).await
.map_err(ControllerInitError::dependency)?;
Ok(Self { policy })
}
}
#[controller]
impl GreetingController {
#[get("/")]
async fn read(&self, Service(service): Service<GreetingService>)
-> Result<String, HttpApiError>
{
Ok(service.message())
}
}A successful GET /greetings returns "Merhaba" as a JSON string. For a plain-text response, use PlainText; see responses.
Constructor or action?#
The controller constructor runs once in the application's lifetime. You can receive Singleton dependencies here. Do not retain a request-scoped dependency here; resolve it in the action with Service<T>. Services within the same request share the same scope.
Service fields#
#[inject] fields must be Arc<T> or Arc<dyn Trait>. Default is not required if every named field is injected. If there is a runtime field that is not injected, the struct must provide Default. The canonical shape for this derive is a named-field struct or a unit struct; generic services, tuple structs, and enums are not supported.
Initialization and cleanup#
ServiceTrait::initialize(&mut self) runs when the object is created, and dispose(&self) runs when its owner closes. Do not call them manually from a handler. If shutdown requires mutable state, use synchronization suitable for shared access.
#[service(disabled)] disables a service candidate at compile time; it is not a feature toggle changed during a request.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.