How does tower::Layer trait enable middleware composition in service-oriented architectures?
tower::Layer is a trait that defines middleware as a transformation from one Service to another, enabling composable stacks where each layer wraps the inner service and can intercept requests, responses, or both. This pattern decouples middleware logic from service implementations, allowing independent layers to be combined in a declarative way without tightly coupling them to specific service types.
The Service Trait Foundation
use tower::Service;
use std::task::{Context, Poll};
use std::future::Future;
use std::pin::Pin;
// The Service trait is the foundation:
pub trait Service<Request> {
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, req: Request) -> Self::Future;
}
// A simple service implementation:
struct EchoService;
impl Service<String> for EchoService {
type Response = String;
type Error = std::convert::Infallible;
type Future = std::future::Ready<Result<String, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: String) -> Self::Future {
std::future::ready(Ok(req))
}
}Service defines an asynchronous function from Request to Result<Response, Error>.
The Layer Trait Definition
use tower::Service;
// The Layer trait transforms one service into another:
pub trait Layer<S> {
type Service: Service<Request>;
type Request;
fn layer(&self, inner: S) -> Self::Service;
}
// Layer takes an inner service and returns a new service
// The new service wraps the inner service, adding behaviorLayer::layer transforms a service, wrapping it with additional behavior.
Basic Layer Implementation
use tower::Service;
use std::task::{Context, Poll};
use std::pin::Pin;
// A middleware that logs requests:
struct LoggingLayer;
impl<S> Layer<S> for LoggingLayer
where
S: Service<String>,
{
type Service = LoggingService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
LoggingService { inner }
}
}
// The wrapped service:
struct LoggingService<S> {
inner: S,
}
impl<S> Service<String> for LoggingService<S>
where
S: Service<String>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: String) -> Self::Future {
println!("Received request: {}", req);
self.inner.call(req)
}
}
// Usage:
fn basic_layer_usage() {
let inner = EchoService;
let logging_layer = LoggingLayer;
let service = logging_layer.layer(inner);
// Now we have a LoggingService<EchoService>
}Layer creates a new service type that wraps the inner service.
Composing Multiple Layers
use tower::ServiceBuilder;
// Layer composition with ServiceBuilder:
fn compose_layers() {
let service = ServiceBuilder::new()
.layer(LoggingLayer)
.layer(TimingLayer)
.layer(RetryLayer::new(3))
.service(EchoService);
// Layers are applied bottom-up:
// RetryLayer wraps TimingLayer wraps LoggingLayer wraps EchoService
// Request flows: LoggingLayer -> TimingLayer -> RetryLayer -> EchoService
// Response flows: EchoService -> RetryLayer -> TimingLayer -> LoggingLayer
}
// Or use tower::ServiceExt:
use tower::ServiceExt;
fn compose_with_service() {
let service = EchoService
.layer(LoggingLayer)
.layer(TimingLayer)
.layer(RetryLayer::new(3));
}ServiceBuilder provides a fluent API for composing layers.
Layer Composition Order
use tower::ServiceBuilder;
fn layer_ordering() {
// Layers are applied in order, but execution is inside-out:
let service = ServiceBuilder::new()
.layer(A::new()) // Applied third (outermost)
.layer(B::new()) // Applied second
.layer(C::new()) // Applied first (innermost)
.service(Inner);
// Request flow: A -> B -> C -> Inner
// Response flow: Inner -> C -> B -> A
// This means:
// - A sees the request first, response last
// - C sees the request last before Inner, response first after Inner
}
struct A;
struct B;
struct C;
struct Inner;
impl<S> Layer<S> for A {
type Service = AService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service { AService { inner } }
}
impl<S> Layer<S> for B {
type Service = BService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service { BService { inner } }
}
impl<S> Layer<S> for C {
type Service = CService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service { CService { inner } }
}
struct AService<S> { inner: S }
struct BService<S> { inner: S }
struct CService<S> { inner: S }Layer order determines request/response processing order.
Request Transformation Layer
use tower::Service;
use std::task::{Context, Poll};
// Layer that transforms requests:
struct AddHeaderLayer {
header_name: String,
header_value: String,
}
impl AddHeaderLayer {
fn new(name: &str, value: &str) -> Self {
Self {
header_name: name.to_string(),
header_value: value.to_string(),
}
}
}
impl<S> Layer<S> for AddHeaderLayer {
type Service = AddHeaderService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
AddHeaderService {
inner,
header_name: self.header_name.clone(),
header_value: self.header_value.clone(),
}
}
}
struct AddHeaderService<S> {
inner: S,
header_name: String,
header_value: String,
}
impl<S, B> Service<http::Request<B>> for AddHeaderService<S>
where
S: Service<http::Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: http::Request<B>) -> Self::Future {
req.headers_mut().insert(
&self.header_name,
http::HeaderValue::from_str(&self.header_value).unwrap(),
);
self.inner.call(req)
}
}Layers can modify requests before they reach the inner service.
Response Transformation Layer
use tower::Service;
use std::task::{Context, Poll};
use std::pin::Pin;
use std::future::Future;
// Layer that transforms responses:
struct TimeoutLayer {
duration: std::time::Duration,
}
impl TimeoutLayer {
fn new(duration: std::time::Duration) -> Self {
Self { duration }
}
}
impl<S> Layer<S> for TimeoutLayer {
type Service = TimeoutService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
TimeoutService {
inner,
duration: self.duration,
}
}
}
struct TimeoutService<S> {
inner: S,
duration: std::time::Duration,
}
impl<S, Request> Service<Request> for TimeoutService<S>
where
S: Service<Request>,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
let duration = self.duration;
let inner_future = self.inner.call(req);
Box::pin(async move {
match tokio::time::timeout(duration, inner_future).await {
Ok(result) => result,
Err(_) => panic!("Timeout exceeded"), // In practice, return error
}
})
}
}Layers can wrap futures to transform responses or handle errors.
Error Handling Layer
use tower::Service;
use std::task::{Context, Poll};
use std::pin::Pin;
use std::future::Future;
// Layer that converts errors:
struct MapErrLayer<F>(F);
impl<F> MapErrLayer<F> {
fn new(f: F) -> Self {
Self(f)
}
}
impl<S, F> Layer<S> for MapErrLayer<F>
where
F: Clone,
{
type Service = MapErrService<S, F>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
MapErrService {
inner,
f: self.0.clone(),
}
}
}
struct MapErrService<S, F> {
inner: S,
f: F,
}
impl<S, F, Request, E> Service<Request> for MapErrService<S, F>
where
S: Service<Request>,
F: Fn(S::Error) -> E + Clone,
{
type Response = S::Response;
type Error = E;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self.inner.poll_ready(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
Poll::Ready(Err(e)) => Poll::Ready(Err((self.f)(e))),
Poll::Pending => Poll::Pending,
}
}
fn call(&mut self, req: Request) -> Self::Future {
let inner_future = self.inner.call(req);
let f = self.f.clone();
Box::pin(async move {
inner_future.await.map_err(f)
})
}
}Layers can transform errors to make them compatible across services.
Stateful Layers
use tower::Service;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
// Layer that maintains shared state:
struct CounterLayer {
counter: Arc<AtomicUsize>,
}
impl CounterLayer {
fn new() -> Self {
Self {
counter: Arc::new(AtomicUsize::new(0)),
}
}
}
impl<S> Layer<S> for CounterLayer {
type Service = CounterService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
CounterService {
inner,
counter: self.counter.clone(),
}
}
}
struct CounterService<S> {
inner: S,
counter: Arc<AtomicUsize>,
}
impl<S, Request> Service<Request> for CounterService<S>
where
S: Service<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
self.counter.fetch_add(1, Ordering::SeqCst);
self.inner.call(req)
}
}
// Access counter from multiple services:
fn stateful_layer() {
let layer = CounterLayer::new();
let service1 = layer.layer(EchoService);
let service2 = layer.layer(EchoService);
// Both services share the same counter
}Layers can share state across multiple service instances.
Built-in Tower Layers
use tower::ServiceBuilder;
use tower::limit::{RateLimitLayer, concurrency::ConcurrencyLimitLayer};
use tower::retry::{RetryLayer, Policy};
use tower::timeout::TimeoutLayer;
use std::time::Duration;
fn built_in_layers() {
let service = ServiceBuilder::new()
// Rate limiting: max 10 requests per second
.layer(RateLimitLayer::new(10, Duration::from_secs(1)))
// Concurrency limit: max 100 concurrent requests
.layer(ConcurrencyLimitLayer::new(100))
// Timeout: fail after 30 seconds
.layer(TimeoutLayer::new(Duration::from_secs(30)))
// Retry: retry failed requests
.layer(RetryLayer::new(MyRetryPolicy))
// Service at the bottom
.service(MyService);
}
struct MyService;
struct MyRetryPolicy;
impl<Req> Policy<Req> for MyRetryPolicy {
type Future = std::future::Ready<()>;
fn retry(&self, _req: &Req, result: Result<&(), &()>) -> Option<Self::Future> {
match result {
Ok(_) => None, // Success, don't retry
Err(_) => Some(std::future::ready(())), // Failure, retry
}
}
fn clone_request(&self, req: &Req) -> Option<Req> where Req: Clone {
Some(req.clone())
}
}Tower provides many built-in layers for common middleware patterns.
Tower-HTTP Layers
use tower_http::{
trace::TraceLayer,
compression::CompressionLayer,
cors::CorsLayer,
request_id::RequestIdLayer,
};
use tower::ServiceBuilder;
fn http_layers() {
let service = ServiceBuilder::new()
// Add request ID
.layer(RequestIdLayer)
// Tracing/logging
.layer(TraceLayer::new_for_http())
// CORS handling
.layer(CorsLayer::permissive())
// Response compression
.layer(CompressionLayer::new())
.service(MyHttpService);
}
struct MyHttpService;tower-http provides HTTP-specific layers for web services.
Layer Factory Pattern
use tower::Layer;
use std::sync::Arc;
// Some layers need to create per-request state:
struct AuthLayer {
secret: Arc<String>,
}
impl AuthLayer {
fn new(secret: &str) -> Self {
Self {
secret: Arc::new(secret.to_string()),
}
}
}
impl<S> Layer<S> for AuthLayer {
type Service = AuthService<S>;
type Request = S::Request;
fn layer(&self, inner: S) -> Self::Service {
AuthService {
inner,
secret: self.secret.clone(),
}
}
}
struct AuthService<S> {
inner: S,
secret: Arc<String>,
}
// Each call to .layer() creates a new service with the same config:
fn layer_factory() {
let auth_layer = AuthLayer::new("my-secret-key");
let service1 = auth_layer.layer(ServiceA);
let service2 = auth_layer.layer(ServiceB);
// Both services use the same secret
}Layers act as factories, creating configured services from inner services.
Testing with Layers
use tower::Service;
use tower::ServiceBuilder;
#[cfg(test)]
mod tests {
use super::*;
use tower::ServiceExt; // For .oneshot()
#[tokio::test]
async fn test_layered_service() {
// Build test service with layers:
let service = ServiceBuilder::new()
.layer(LoggingLayer)
.layer(TimingLayer)
.service(EchoService);
// Use oneshot for single request:
let response = service.oneshot("test".to_string()).await.unwrap();
assert_eq!(response, "test");
}
#[tokio::test]
async fn test_layer_in_isolation() {
// Test just the layer:
let inner = MockService;
let layer = LoggingLayer;
let service = layer.layer(inner);
let response = service.oneshot("test".to_string()).await.unwrap();
assert_eq!(response, "test");
}
}
struct MockService;
impl Service<String> for MockService {
type Response = String;
type Error = std::convert::Infallible;
type Future = std::future::Ready<Result<String, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: String) -> Self::Future {
std::future::ready(Ok(req))
}
}Test layers individually or in combination using ServiceExt::oneshot.
Real-World Example: Web Service Middleware Stack
use tower::ServiceBuilder;
use tower_http::{
trace::TraceLayer,
cors::CorsLayer,
compression::CompressionLayer,
request_id::RequestIdLayer,
sensitive_headers::SetSensitiveRequestHeadersLayer,
};
use std::time::Duration;
fn production_stack() {
let sensitive_headers = vec![http::header::AUTHORIZATION];
let service = ServiceBuilder::new()
// Request ID for tracing
.layer(RequestIdLayer)
// Security: remove sensitive headers from logs
.layer(SetSensitiveRequestHeadersLayer::new(sensitive_headers))
// Logging and tracing
.layer(TraceLayer::new_for_http())
// CORS for browser clients
.layer(CorsLayer::permissive())
// Compression for large responses
.layer(CompressionLayer::new())
// Rate limiting
.layer(tower::limit::RateLimitLayer::new(100, Duration::from_secs(1)))
// Concurrency limit
.layer(tower::limit::ConcurrencyLimitLayer::new(1000))
// Timeout
.layer(tower::timeout::TimeoutLayer::new(Duration::from_secs(30)))
// Actual handler
.service(HandlerService);
}
struct HandlerService;Production services compose multiple layers for reliability, security, and observability.
Comparison: Layer vs Middleware Trait
// Approach 1: Layer (Tower style)
// - Layer is a factory that creates Service instances
// - Each layer produces a new Service type
// - Composed at compile time
// - Zero runtime overhead
// Approach 2: Dynamic middleware (trait object)
// - Runtime composition
// - Dynamic dispatch overhead
// - More flexible but slower
fn comparison() {
// Tower's Layer approach:
let service = ServiceBuilder::new()
.layer(A::new())
.layer(B::new())
.service(Inner);
// Type: BService<AService<InnerService<Inner>>>
// Fully typed, inlined, zero runtime dispatch
// Dynamic approach:
let service: Box<dyn Service<Request>> = Box::new(Inner);
let service: Box<dyn Service<Request>> = Box::new(A { inner: service });
let service: Box<dyn Service<Request>> = Box::new(B { inner: service });
// Dynamic dispatch on every call
}Tower's Layer uses static dispatch for maximum performance.
Key Points
fn key_points() {
// 1. Layer trait transforms Service<S> into Service<Request>
// 2. Layers wrap inner services, adding behavior
// 3. Layer::layer() is a factory method creating new service types
// 4. ServiceBuilder provides fluent composition API
// 5. Layers are applied bottom-up, requests flow top-down
// 6. Each layer can intercept request, response, or both
// 7. Layers can share state across service instances
// 8. Built-in layers: timeout, retry, rate-limit, etc.
// 9. tower-http provides HTTP-specific layers
// 10. Layer composition is compile-time, zero runtime dispatch
// 11. ServiceExt::oneshot() for single-request testing
// 12. poll_ready() propagates backpressure through layers
// 13. Layers can transform errors
// 14. Stateful layers use Arc for shared state
// 15. Layer pattern enables reusable, composable middleware
}Key insight: tower::Layer implements the decorator pattern in a way that composes at compile time through type transformations. Each Layer::layer call produces a new service type wrapping the inner service, creating a chain of nested types. When you call ServiceBuilder::new().layer(A).layer(B).service(Inner), you get BService<AService<InnerService>>. This static composition means the compiler can inline and optimize the entire middleware chain, with no runtime dispatch overhead. The pattern is particularly powerful because each layer is independent—A doesn't know about B, and B doesn't know about Inner. They simply wrap a service and implement Service themselves. This enables a rich ecosystem of reusable middleware where any layer can be combined with any other layer, and the order of composition determines the order of request and response processing. The poll_ready method propagates backpressure through the chain, allowing layers to signal when they're unavailable (e.g., rate limiter at capacity), making the middleware stack a cooperative participant in async resource management.
