Lifecycle handlers

Roadster provides the AppLifecycleHandler trait to allow apps to hook into various stages of their lifecycle to perform any custom setup or teardown logic. Roadster also implements some of its built-in lifecycle logic using this trait; for example, DB migrations are performed by the DbMigrationLifecycleHandler. Similar to the HTTP middleware and initializers, lifecycle handlers are run in priority order, and the priority of each handler can be customized via the app's config files.

Lifecycle hooks

AppLifecycleHandlers have various hooks to allow managing resources at a particular point in the process of the app's lifecycle. They are listed below in the order in which they are run:

  1. before_health_checks: Runs right before the app's health checks are run during startup.
  2. before_services: Runs right before the app's services are started.
  3. on_shutdown: Runs when the app is shutting down after all the app's services have been stopped.

Default lifecycle handlers

For the most up to date list of lifecycle handlers provided by Roadster, see the module's docs.rs page

All of the default lifecycle handlers can be configured via the app's config files. All lifecycle handlers have at least the following config fields:

  • enable: Whether the lifecycle handlers is enabled. If not provided, the lifecycle handlers enablement falls back to the value of the lifecycle-handler.default-enable field.
  • priority: The priority in which the lifecycle handlers will run. Lower values (including negative numbers) run before higher values. The lifecycle handlers provided by Roadster have priorities between -10,000 (runs first) and 10,000 (runs later) by default, though these values can be overridden via configs. If the order your lifecycle handlers runs in doesn't matter, simply set to 0.

Custom middleware

Implement AppLifecycleHandler

Custom middleware can be provided by implementing the AppLifecycleHandler trait.

pub struct ExampleLifecycleHandler;

#[async_trait]
impl AppLifecycleHandler<RoadsterApp<AppContext>, AppContext> for ExampleLifecycleHandler {
    fn name(&self) -> String {
        "example".to_owned()
    }

    fn enabled(&self, state: &AppContext) -> bool {
        // Custom lifecycle handlers can be enabled/disabled via the app config
        // just like built-in handlers
        state
            .config()
            .lifecycle_handler
            .custom
            .get(&self.name())
            .map(|config| config.common.enabled(&state))
            .unwrap_or_else(|| state.config().health_check.default_enable)
    }

    fn priority(&self, state: &AppContext) -> i32 {
        state
            .config()
            .lifecycle_handler
            .custom
            .get(&self.name())
            .map(|config| config.common.priority)
            .unwrap_or_default()
    }

    async fn before_health_checks(
        &self,
        _prepared_app: &PreparedAppWithoutCli<RoadsterApp<AppContext>, AppContext>,
    ) -> RoadsterResult<()> {
        todo!("Implement in order to initialize some state before health checks run")
    }

    async fn before_services(
        &self,
        _prepared_app: &PreparedAppWithoutCli<RoadsterApp<AppContext>, AppContext>,
    ) -> RoadsterResult<()> {
        todo!("Implement in order to initialize some state before the app's services are started")
    }

    async fn on_shutdown(&self, _state: &AppContext) -> RoadsterResult<()> {
        todo!("Implement in order to perform any necessary clean up on app shutdown")
    }
}

Register the handler

In order to run for the handler to run, it needs to be registered with the app.

fn build_app() -> RoadsterApp<AppContext> {
    RoadsterApp::builder()
        // Use the default `AppContext` for this example
        .state_provider(|context| Ok(context))
        // Register custom lifecycle handler(s)
        .add_lifecycle_handler_provider(|registry, _state| {
            registry.register(ExampleLifecycleHandler)?;
            Ok(())
        })
        .add_service_provider(move |_registry, _state| {
            Box::pin(async move { todo!("Add services here.") })
        })
        .build()
}