lilydocs
HTTP APIWebSocketConsumer
Fundamentals3 min read

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:

TOML
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.

di.rs
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.

QUICK START
Documentation
Source code