Providers asíncronos
A veces un provider no se puede crear hasta que termina una operación asíncrona: abrir un pool de conexiones, descargar una clave pública, leer un archivo de configuración. En Corvis toda la construcción del contenedor es asíncrona, así que no hace falta nada especial: una fábrica de Provider::use_factory es un async y el arranque espera a que termine.
use corvis::prelude::*;
#[module(
providers = [
Provider::use_factory("JWKS", |ctx| async move {
let config = ctx.get::<ConfigService>().await?;
let path: String = config.get_or("JWKS_PATH", "./jwks.json".to_string());
let raw = corvis::tokio::fs::read_to_string(&path).await?;
let keys: Value = corvis::serde_json::from_str(&raw)?;
Ok(keys)
}),
KeysService,
],
)]
pub struct KeysModule;
#[injectable]
pub struct KeysService {
#[inject("JWKS")]
jwks: Arc<Value>,
}Puntos a tener en cuenta:
- Se resuelve una vez. Una fábrica con scope por defecto se ejecuta una sola vez, al arrancar, y todos los consumidores reciben el mismo
Arc. - Las dependencias esperan. Si
KeysServicepide"JWKS", el contenedor ejecuta la fábrica primero. El orden de la listaprovidersno importa. - Los errores detienen el arranque. Un
Errde la fábrica hace queCorvisFactory::createdevuelva el error, envuelto comoError while instantiating JWKS: ..., y lo registra con el loggerExceptionHandler. La aplicación no llega a escuchar. - No bloquees el runtime. La fábrica corre dentro de Tokio: usa
tokio::fs, clientes asíncronos otokio::task::spawn_blockingpara trabajo bloqueante.
Un ejemplo real: la base de datos de Tertulia
OrmModule::for_root_async recibe una fábrica asíncrona que arma las opciones de conexión a partir de ConfigService. El módulo registra esas opciones con Provider::use_factory y, a partir de ellas, crea OrmService:
OrmModule::for_root_async(true, |ctx| async move {
let config = ctx.get::<ConfigService>().await?;
let url: String = config.get_or("DATABASE_URL", "file:./tertulia.db".to_string());
Ok(OrmModuleOptions::new(url)
.log_queries(config.get_or("LOG_QUERIES", false))
.push::<(Room, ChatMessage)>())
})JwtModule::register_async sigue el mismo patrón para construir JwtService con el secreto que viene de la configuración. Cómo escribir tus propios métodos *_async está en Módulos dinámicos.
Trabajo asíncrono después de crear el provider
Si el provider se puede construir enseguida y lo asíncrono es una preparación posterior, impleméntala en un hook del ciclo de vida en lugar de en la fábrica. RoomsService de Tertulia crea las salas por defecto en on_application_bootstrap, cuando todas las dependencias ya existen:
#[async_trait]
impl OnApplicationBootstrap for RoomsService {
async fn on_application_bootstrap(&self) -> corvis::Result<()> {
for (name, topic) in DEFAULT_ROOMS {
if self.find(name).await?.is_none() {
// crea la sala
}
}
Ok(())
}
}