Quick Start
One store, end to end: etcd holds the overrides, a file holds the base, and a change in the cluster reaches a running process.
[dependencies]
dynamic-config = { version = "<version>", features = ["toml", "json", "watch"] }
dynamic-config-etcd = "<version>"
use std::time::Duration;
use dynamic_config::dynamic_config;
use dynamic_config_etcd::Etcd;
use serde::Deserialize;
#[dynamic_config]
#[derive(Debug, Deserialize)]
struct AppConfig {
host: String,
port: u16,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Etcd::new(["http://etcd:2379"]).key("myapp/config.json");
let builder = AppConfig::builder("app")
.file("config.toml") // the base, from disk — later wins
.remote(store); // what the cluster says, on top
builder.init()?; // one fetch, fail fast if neither loads
// Poll the store on its own cadence; file watching is separate and
// both can run at once.
AppConfig::refresh_remote(Duration::from_secs(15))?;
println!("{}:{}", AppConfig::current().host, AppConfig::current().port);
Ok(())
}
Try it without a cluster: docker run -p 2379:2379 quay.io/coreos/etcd
and etcdctl put myapp/config.json '{"app": {"port": 9000}}' — fifteen
seconds later, current() answers 9000.
Three rules carry from here to every store in this book:
- A store hands over a document; the engine does the rest. Layering,
validation,
explain, the last-known-good cache — all identical to a file source, which is why a store outage is survivable: the LKG chapter belongs to the engine and applies unchanged. - The key's extension names the format —
config.jsonis JSON,config.propertiesis properties — andwith_formatexists for keys that name nothing. - Push stores watch, pull stores poll. etcd, Consul, NATS and Redis
can push (
watch_remote); S3, Firestore and git poll; the at-a-glance table says which is which.
From here: Remote Stores for the shared machinery, your store's own chapter for its auth and its shape, and The Config Server for the program that can reach none of these and asks over HTTP instead.