Layered
Layered Services
Build composable async services with layered middleware.
This crate provides the Service trait and a layer system for adding cross-cutting
concerns like timeouts, retries, and logging.
Why not Tower?
Tower predates async fn in traits, requiring manual Future types
or boxing and poll_ready back-pressure semantics. Tower’s &mut self also requires cloning
for concurrent requests. This crate uses async fn with &self, enabling simpler middleware
and natural concurrency. Tower interop is available via the tower-service feature.
Quick Start
A Service transforms an input into an output asynchronously:
use Service;
;
Use Execute to turn any async function into a service:
use ;
let greeter = new;
assert_eq!;
Key Concepts
- Service: A type implementing the
Servicetrait that transforms inputs into outputs asynchronously. Think of it asasync fn(&self, In) -> Out. - Middleware: A service that wraps another service to add cross-cutting behavior such as logging, timeouts, or retries. Middleware receives inputs before the inner service and can process outputs after.
- Layer: A type implementing the
Layertrait that constructs middleware around a service. Layers are composable and can be stacked using tuples like(layer1, layer2, service).
Layers and Middleware
A Layer wraps a service with additional behavior. In this example, we create a logging
middleware that prints inputs before passing them to the inner service:
use ;
// A simple logging layer
;
;
// Stack layers with the service (layers apply outer to inner)
let service = .into_service;
let result = service.execute.await;
Thread Safety
All services must implement Send and Sync, and returned futures must be Send.
This ensures compatibility with multi-threaded async runtimes like Tokio.
Features
intercept: EnablesInterceptmiddlewaredynamic-service: EnablesDynamicServicefor type erasuretower-service: Enables Tower interoperability via thetowermodule