← Week 2: Tower Middleware Stack

Day 8: Tower Service Trait

Phase 2 · Jun 17, 2026

← Week 2: Tower Middleware Stack

Agenda (2–3 hours)

  • Read (45 min): Tower crate documentation; tower::Service trait source; Carl Lerche's "Tower: A library of reusable asynchronous components" blog post
  • Study (45 min): How does ServiceBuilder chain layers? Trace a request through a two-layer stack
  • Practice (45 min): Implement a simple Service<String> that uppercases input; wrap it in a logging Layer
  • Challenge (30 min): Implement BoxService manually — why does boxing require Send + Sync + 'static?
← Week 2: Tower Middleware Stack

Tower's Core Abstraction

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;
}

poll_ready: check if the service can accept a request (backpressure / resource availability).
call: dispatch the request; returns a future.

← Week 2: Tower Middleware Stack

Layer: The Middleware Primitive

pub trait Layer<S> {
    type Service;
    fn layer(&self, inner: S) -> Self::Service;
}

A Layer wraps an inner service and returns a new service. Compose with ServiceBuilder:

let service = ServiceBuilder::new()
    .layer(TimeoutLayer::new(Duration::from_secs(5)))
    .layer(RateLimitLayer::new(100, Duration::from_secs(1)))
    .layer(ConcurrencyLimitLayer::new(50))
    .service(my_service);
// Layers applied in order: concurrency → rate → timeout → service
← Week 2: Tower Middleware Stack

poll_ready and Backpressure

poll_ready is crucial for backpressure:

// Correct usage pattern
service.ready().await?; // convenience: calls poll_ready internally
let response = service.call(request).await?;

ConcurrencyLimitService: returns Poll::Pending when all permits are taken.
RateLimitService: returns Poll::Pending when rate is exceeded.

Common mistake: calling call without checking poll_ready may panic on services that require readiness (e.g., after reservation).

← Week 2: Tower Middleware Stack

BoxService and BoxCloneService

// Erase the type of a service (for dynamic dispatch)
use tower::util::BoxService;

let boxed: BoxService<Request, Response, Error> = BoxService::new(my_service);

// For services that need to be cloned (e.g., per-connection)
use tower::util::BoxCloneService;
let cloneable: BoxCloneService<Request, Response, Error> = BoxCloneService::new(my_service);

Used when you need to store services in a collection or return them from a function without knowing the concrete type.

← Week 2: Tower Middleware Stack

Key Takeaways

  • Service<Request> is Tower's universal abstraction for request-response components
  • Layer composes services: each layer wraps the inner service, adding one behavior
  • poll_ready implements backpressure — the service signals whether it can accept a request
  • ServiceBuilder stacks layers in a readable, type-safe way
  • Axum, tonic (gRPC), and Hyper (HTTP) all accept Tower services as handlers

Tomorrow: implementing timeout middleware as a Tower Layer.