diode-base 0.3.0

Basic services for the diode library
Documentation

diode-base

Application building blocks for the diode dependency-injection framework: layered configuration, long-running daemons, a CLI entry point, structured tracing and metrics, and dynamic configuration.

[dependencies]
diode = "0.2"
diode-base = "0.3"

What it provides

  • Configuration - Config loads and merges layered JSON. Declare a typed section with #[config_section("name")] and read it with config.get.
  • Daemons - the Daemon trait, AddDaemonExt / AddDaemonServiceExt to register background tasks, and RunDaemonsExt::run_daemons to run them concurrently with cooperative, token-based shutdown.
  • CLI - the Command trait, AddCommandExt, and RunMainExt::run_main, which parses arguments, loads config, sets up tracing/metrics, builds the app, and dispatches a subcommand. Built-in server runs every daemon; config prints the resolved configuration.
  • Observability - Tracing and Metrics wire up tracing and OpenTelemetry (OTLP) exporters from the tracing / metrics config sections.
  • Dynamic configuration - watch config sources and react to changes at runtime (for example to change the tracing level live).
  • Testing - the testing module ships integration-test helpers such as FreePort.

Example

A worker service driven by configuration, wired as a daemon and started through the CLI entry point:

use std::process::ExitCode;
use std::sync::Arc;
use std::time::Duration;

use diode::{App, AppContext, Service, StdError};
use diode_base::{
    config_section, AddDaemonServiceExt, CancellationToken, Config, Daemon, RunMainExt,
};
use serde::{Deserialize, Serialize};

// A typed configuration section, read from the `worker` key of the config file.
#[derive(Serialize, Deserialize)]
#[config_section("worker")]
struct WorkerConfig {
    interval_secs: u64,
}

// A background worker: a `Service` (built from the container, reading its
// config) that is also a `Daemon` (runs until shutdown).
struct Worker {
    interval: Duration,
}

impl Service for Worker {
    type Handle = Arc<Self>;

    async fn build(ctx: &AppContext) -> Result<Self::Handle, StdError> {
        let config: WorkerConfig = ctx
            .get_component_ref::<Config>()
            .ok_or("config component missing")?
            .get("worker")?;
        Ok(Arc::new(Self {
            interval: Duration::from_secs(config.interval_secs),
        }))
    }
}

impl Daemon for Worker {
    async fn run(&self, _app: &App, shutdown: CancellationToken) -> Result<(), StdError> {
        loop {
            tokio::select! {
                _ = shutdown.cancelled() => break,
                _ = tokio::time::sleep(self.interval) => tracing::info!("tick"),
            }
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() -> ExitCode {
    let mut builder = App::builder();
    builder.add_daemon_service::<Worker>();
    // `run_main` parses CLI args, loads the config file, sets up tracing and
    // metrics, then runs the selected command (the built-in `server` command
    // runs every registered daemon until Ctrl-C).
    builder.run_main().await
}

Features

  • macros (default) - the #[config_section(..)] attribute macro.

License

Licensed under either of MIT or Apache-2.0 at your option.