Stacking Layers — Composition Patterns
Individual layers do one thing. A real application needs them composed. id_effect provides two composition patterns: sequential stacking and parallel merging.
Sequential Stacking with .stack()
#![allow(unused)] fn main() { use id_effect::Stack; let app_env = config_layer .stack(db_layer) // Config → (Config, Database) .stack(logger_layer) // → (Config, Database, Logger) .stack(service_layer); // → (Config, Database, Logger, Service) }
Each .stack() takes the output of the previous layer and combines it with the new layer's output. The final type accumulates everything.
.stack() implies sequential ordering: db_layer runs after config_layer completes, because db_layer needs what config_layer produces.
Parallel Merging with merge_all
When layers don't depend on each other, they can be built in parallel:
#![allow(unused)] fn main() { use id_effect::merge_all; // These three layers are independent — build them concurrently let monitoring = merge_all!( metrics_layer, tracing_layer, health_check_layer, ); }
merge_all! takes a list of layers with the same input type and merges their outputs. If the inputs are available, all three build concurrently.
Combining Stack and merge_all
In practice, you mix both:
#![allow(unused)] fn main() { let app_env = config_layer .stack(db_layer) .stack( // cache and redis are independent of each other but both need config+db merge_all!(cache_layer, redis_layer) ) .stack(service_layer); }
The build graph:
- Build config
- Build db (needs config)
- Build cache and redis concurrently (both need config + db)
- Build service (needs all of the above)
Building and Providing
Once you have a composed layer, build it and provide it to an effect:
#![allow(unused)] fn main() { // Build all layers, get back a Context with everything let env = app_env.build(()).await?; // Provide to the effect and run run_blocking(my_application().provide(env)); }
Or use .provide_layer() directly on an effect:
#![allow(unused)] fn main() { run_blocking( my_application() .provide_layer(app_env) ); }
.provide_layer() builds the layer and provides its output in one step. This is the most common pattern at the application entry point.
The type_only Pattern for Tests
Tests often want a subset of services. You can stack only what the test needs:
#![allow(unused)] fn main() { #[test] fn test_user_service() { let test_layer = config_layer.stack(mock_db_layer); let result = run_test( get_user(1) .provide_layer(test_layer) ); assert!(result.is_ok()); } }
No need to build the full application stack. The test provides exactly what get_user requires — the type system enforces completeness.