async-injector
Reactive dependency injection for Rust.
This project is work in progress. APIs will change.
Rationale
This component was extracted and cleaned up from OxidizeBot
.
There it served as a central point for handling live reconfiguration of the
application, without the need to restart it.
This crate provides a reactive dependency injection system that can reconfigure
your application dynamically from changes in your dependencies.
Using an Injector
to deal with this complexity provides a nice option for a
clean architecture.
It also provides a natural way for structuring an application that gracefully
degrades during the absence of configuration: any part of the dependency tree
that is not available can simply not be configured.
Example
The following is an example application that receives configuration changes
over HTTP.
#![feature(async_await)]
use failure::Error;
use async_injector::{Provider, Injector, Key};
#[derive(Provider)]
struct DatabaseProvider {
#[dependency(tag = "database/url")]
url: String,
#[dependency(tag = "database/connection-limit")]
connection_limit: u32,
}
impl DatabaseProvider {
async fn clear(injector: &Injector) {
injector.clear::<Database>();
}
async fn build(self, injector: &Injector) {
let database = match Database::connect(&self.url, self.connection_limit).await {
Ok(database) => database,
Err(e) => {
log::warn!("failed to connect to database: {}", self.url);
injector.clear::<Database>();
return;
}
}
injector.update(database);
}
}
async fn serve(injector: &Injector) -> Result<(), Error> {
let server = Server::new()?;
server.on("POST", "/config/database/url", |url: String| {
injector.update_key(Key::tagged("database/url"), url);
});
server.on("POST", "/config/database/connection-limit", |limit: u32| {
injector.update_key(Key::tagged("database/connection-limit"), limit);
});
server.await?;
Ok(())
}
#[runtime::main]
async fn main() -> Result<(), Error> {
let injector0 = Injector::new();
let injector = injector0.clone();
runtime::spawn(async move {
DatabaseProvider::run(&injector0).await;
});
let injector = injector0.clone();
runtime::spawn(async move {
serve(&injector).await.expect("web server errored");
})
let (database_stream, database) = injector0.stream::<Database>();
let application = Application::new(database);
loop {
futures::select {
database = database_stream.next() => {
application.database = database;
}
_ = application => {
log::info!("application finished");
}
}
}
}