Eventos del ciclo de vida
Una aplicación de Corvis pasa por tres fases: arranque (se crea el contenedor y se instancian los providers), ejecución (atiende peticiones) y apagado. En los límites entre fases Corvis llama a hooks que tus providers, controladores y módulos pueden implementar para preparar o liberar recursos.
Los hooks
Cada hook es un trait asíncrono:
| Trait | Método | Cuándo se llama |
|---|---|---|
OnModuleInit | on_module_init(&self) | Al inicializar, con todas las dependencias ya resueltas |
OnApplicationBootstrap | on_application_bootstrap(&self) | Después de todos los on_module_init, antes de aceptar conexiones |
OnModuleDestroy | on_module_destroy(&self) | Al cerrar la aplicación (app.close() o una señal) |
BeforeApplicationShutdown | before_application_shutdown(&self, signal: Option<String>) | Después de todos los on_module_destroy |
OnApplicationShutdown | on_application_shutdown(&self, signal: Option<String>) | Al final del apagado |
Todos devuelven corvis::Result<()>. Basta con implementar el trait: #[injectable], #[controller] y #[module] detectan la implementación al compilar, sin registro adicional.
use corvis::prelude::*;
#[injectable]
pub struct RoomsService {
db: Arc<OrmService>,
events: Arc<EventEmitter>,
}
#[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() {
self.db
.room()
.create(json!({ "data": { "name": name, "topic": topic, "createdBy": "tertulia" } }))
.await?;
}
}
Ok(())
}
}(examples/tertulia/src/rooms/rooms_service.rs: crea las salas por defecto al arrancar.)
En examples/tertulia/src/firehose.rs, FirehoseService usa on_application_bootstrap para registrar un listener upgrade en el servidor HTTP: en ese momento el contenedor ya está completo y la aplicación todavía no acepta conexiones.
La secuencia completa
Arranque
CorvisFactory::create::<AppModule>()recorre el grafo de módulos y crea todos los providers y controladores (salvo los de scopeTransient, que se crean al inyectarse). Si algo falla aquí,createdevuelve el error.app.init()(quelistenllama por ti si no lo hiciste) registra las rutas, los middleware, los gateways y los handlers de eventos, y después:- llama a
on_module_initen todos los módulos; - monta las tareas programadas (
#[cron],#[interval],#[timeout]); - llama a
on_application_bootstrapen todos los módulos.
- llama a
listenabre el socket y empieza a atender peticiones.
init() es idempotente: llamarlo dos veces no repite los hooks.
Apagado
app.close().await (o una señal, ver abajo) ejecuta:
on_module_destroyen todos los módulos;- detiene las tareas programadas;
before_application_shutdown(signal);on_application_shutdown(signal).
close() también es idempotente.
Orden entre módulos
Los hooks se recorren por módulo, empezando por los más alejados del módulo raíz (los importados más profundamente) y terminando en el raíz. Dentro de cada módulo el orden es: controladores, providers y, por último, la propia clase del módulo. El apagado sigue el mismo orden que el arranque.
Si un hook devuelve Err, el recorrido se detiene y el error sale de init(), listen() o close().
Qué instancias reciben hooks
- Providers con scope por defecto (singleton) y controladores.
- La clase del módulo (
#[module]también es un provider): un módulo puede implementarOnModuleInitpara tareas de configuración. - No los reciben los providers
Transient(no hay una instancia única que avisar) ni los guards, pipes, interceptors o filtros que Corvis construye bajo demanda sin estar enproviders.
Señales del sistema
Por defecto los hooks de apagado solo corren si llamas a app.close(). Para que la aplicación reaccione a las señales del sistema, actívalo antes de escuchar:
#[corvis::main]
async fn main() -> corvis::Result<()> {
let mut app = tertulia::create_app().await?;
app.enable_shutdown_hooks();
let port: u16 = app.get::<ConfigService>()?.get_or("PORT", 5150);
app.listen(format!("127.0.0.1:{port}")).await
}Con enable_shutdown_hooks(), al recibir la señal:
- el servidor deja de aceptar conexiones y espera a que terminen las que están en curso;
- se ejecuta la secuencia de apagado, con
signaligual aSome("SIGINT")oSome("SIGTERM"); listenretorna.
En Unix se escuchan SIGINT (Ctrl+C) y SIGTERM. En Windows solo Ctrl+C, que se informa como "SIGINT". Cuando cierras con app.close(), signal es None.
#[async_trait]
impl BeforeApplicationShutdown for JobsService {
async fn before_application_shutdown(&self, signal: Option<String>) -> corvis::Result<()> {
self.logger.log(format!("Shutting down ({})", signal.as_deref().unwrap_or("manual")));
self.flush().await
}
}Conexiones durante el apagado
Cuando el apagado lo inicia una señal, las conexiones HTTP ya terminaron antes de llamar a on_module_destroy. Los hooks sirven para cerrar recursos propios (colas, conexiones a bases de datos, archivos), no para seguir atendiendo peticiones.
Contextos sin servidor
CorvisFactory::create_application_context::<M>() crea el contenedor y ejecuta on_module_init y on_application_bootstrap sin registrar rutas ni abrir sockets. Es la base de los scripts y workers (ver Aplicaciones standalone). Cierra con app.close().await para que corran los hooks de apagado.
WARNING
En la versión actual, create_application_context no registra los handlers de #[handlers] (#[on_event], #[cron], #[interval], #[timeout]): ese registro solo ocurre en app.init(). Si tu worker depende de ellos, crea la aplicación con CorvisFactory::create y llama a app.init().await en lugar de usar el contexto standalone.
En los tests, TestingModule::init() y TestingModule::close() disparan las mismas fases (ver Testing).