Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dynamic-config-web

A request-scoped configuration snapshot for Rust web services: one reading per request, however many sections the handler touches.

The book · The engine · docs.rs


[dependencies]
dynamic-config = { version = "0.6", features = ["json", "watch"] }
dynamic-config-axum = "0.3.2"   # or -actix, or -loco
use dynamic_config_axum::{Config, SnapshotLayer};
use dynamic_config_web_core::sections;

async fn index(
    Config(server): Config<Server>,
    Config(features): Config<Features>,
) -> String {
    // One reading, taken when the request began. These two cannot be
    // different generations.
    format!("{}:{} cache={}", server.host, server.port, features.cache)
}

let app = Router::new()
    .route("/", get(index))
    .layer(SnapshotLayer::new(sections![Server, Features]));

The problem

Server::current() is an atomic load, and its own documentation says what to do with it:

Cheap enough to call per request, but call it once per request and reuse the Arc: a reload landing between two calls would otherwise let one request observe two configurations.

With one section that is easy to honour. With two it is not, because "the same generation" is a property of a pair of reads that no single call site can see. A handler that reads Server and then Features can be split by a reload landing between them, and the response then mixes two documents.

These crates take the reading once, before the handler runs, and hand the result to every extractor in it.

The crates

Crate For MSRV
dynamic-config-tower any tower stack — the layer the axum crate is built on 1.88
dynamic-config-axum axum 0.8 — a tower layer and a Config<T> extractor 1.88
dynamic-config-actix Actix Web 4 — a middleware and a FromRequest extractor 1.88
dynamic-config-loco Loco — an Initializer, over the axum crate 1.94 (loco's own floor)
dynamic-config-web-core the shared snapshot. You do not depend on this directly 1.88

1.88 is the organisation's one floor since the 0.2.x stabilisation round; loco alone sits higher because loco does. CI checks each row against the real toolchain.

What these crates do not do

They do not load configuration, watch files, or own a WatchHandle. That stays in the startup code that calls init() and holds the handles — where it already is, and where it belongs. Adding the layer changes no line of it.

They also ship no routes: no /healthz, no /metrics, no diagnostics endpoints. The engine's status(), check() and Exposition are public and a handler over them is four lines, which is a smaller thing to write than a route surface is to adopt.

Sections that are not static

sections![Server, Features] expands to || Server::try_current() for each name, which is what #[dynamic_config] generates. A Dynamic<T> instance works too — register the closure yourself:

let sections = Sections::new()
    .section(Server::try_current)
    .section(move || handle.current());

What you may build on and find unchanged tomorrow is written down: the Compatibility Contract.

License

MIT.

About

One reading of configuration per request, for Rust web services: axum, Actix Web, Loco and plain tower — a snapshot layer and an extractor over dynamic-config.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages