Expand description
§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 layered::Service;
struct Greeter;
impl Service<String> for Greeter {
type Out = String;
async fn execute(&self, name: String) -> Self::Out {
format!("Hello, {name}!")
}
}Use Execute to turn any async function into a service:
use layered::{Execute, Service};
let greeter = Execute::new(|name: String| async move {
format!("Hello, {name}!")
});
assert_eq!(greeter.execute("World".into()).await, "Hello, World!");§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 layered::{Execute, Layer, Service, Stack};
// A simple logging layer
struct LogLayer;
impl<S> Layer<S> for LogLayer {
type Service = LogService<S>;
fn layer(&self, inner: S) -> Self::Service {
LogService(inner)
}
}
struct LogService<S>(S);
impl<S, In: Send + std::fmt::Display> Service<In> for LogService<S>
where
S: Service<In>,
{
type Out = S::Out;
async fn execute(&self, input: In) -> Self::Out {
println!("Input: {input}");
self.0.execute(input).await
}
}
// Stack layers with the service (layers apply outer to inner)
let service = (
LogLayer,
Execute::new(|x: i32| async move { x * 2 }),
).into_service();
let result = service.execute(21).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
Modules§
- prelude
- Common imports for working with layered services.
- tower
tower-service - Tower interoperability for layered services.
Structs§
- Dynamic
Service dynamic-service - Type-erased wrapper for
Servicethat hides the concrete type. - Execute
- Wraps a function or closure as a
Service. - Intercept
intercept - Middleware for observing and modifying service inputs and outputs.
- Intercept
Layer intercept - Builder for creating
Interceptmiddleware.
Traits§
- Dynamic
Service Ext dynamic-service - Extension trait for converting services to
DynamicService. - Layer
- A trait for decorating a
Servicewith middleware. - Service
- An async function
In → Outthat processes inputs. - Stack
- Builds a service from a tuple of layers and a root service.