Building Layers — From Simple to Complex

Simple Layers: One Input, One Output

The simplest layer takes one service and produces another:

#![allow(unused)]
fn main() {
// Produces a database connection from config
let db_layer = LayerFn::new(|config: &Tagged<ConfigKey>| {
    effect! {
        let pool = ~ connect_pool(config.value().db_url());
        tagged::<DatabaseKey>(pool)
    }
});
}

Layers That Need Nothing

A layer that builds from scratch (no inputs) uses Nil or () as its input type:

#![allow(unused)]
fn main() {
// Config layer — reads from environment variables, needs nothing
let config_layer = LayerFn::new(|_: &Nil| {
    effect! {
        let cfg = Config::from_env()?;
        tagged::<ConfigKey>(cfg)
    }
});
}

Layers With Multiple Inputs

To require multiple services, the input type is a tuple of tagged values:

#![allow(unused)]
fn main() {
// Logger needs both Config and MetricsClient
let logger_layer = LayerFn::new(
    |env: &(Tagged<ConfigKey>, Tagged<MetricsKey>)| {
        effect! {
            let config  = env.0.value();
            let metrics = env.1.value();
            let logger  = Logger::new(config.log_level(), metrics.clone());
            tagged::<LoggerKey>(logger)
        }
    }
);
}

Layers That Produce Multiple Values

A layer can produce a context with several services at once:

#![allow(unused)]
fn main() {
// Build both primary and replica database pools
let db_layers = LayerFn::new(|config: &Tagged<ConfigKey>| {
    effect! {
        let primary = ~ connect_pool(config.value().primary_url());
        let replica = ~ connect_pool(config.value().replica_url());
        ctx!(
            tagged::<PrimaryDbKey>(primary),
            tagged::<ReplicaDbKey>(replica),
        )
    }
});
}

Memoization

By default, LayerFn builds fresh on every call. If you want the same instance shared across multiple dependents, wrap with .memoize():

#![allow(unused)]
fn main() {
let shared_config = config_layer.memoize();
// Now every layer that depends on ConfigKey gets the same instance
}

Without memoization, if three layers need ConfigKey, config_layer would run three times. With .memoize(), it runs once and the result is cached for the lifetime of the build.

The Pattern in Practice

A typical application has a handful of layers that form a pipeline:

config_layer (no input)
  → db_layer (needs config)
  → cache_layer (needs config)
  → service_layer (needs db + cache)

The next section shows how to wire them together.