How to Use Tower for Async Service Composition

Tower is a modular toolkit for building robust, reusable async services in Rust, centered around the `Service` and `Layer` traits to compose functionality like logging, timeouts, and metrics.

Tower is a modular toolkit for building robust, reusable async services in Rust, centered around the Service and Layer traits to compose functionality like logging, timeouts, and metrics. You define your core business logic as a Service, then wrap it with Layers to add cross-cutting concerns, creating a flexible pipeline that handles concurrency and error propagation automatically.

Here is a practical example of composing a service with logging and timeout layers:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tower::{Service, ServiceExt, ServiceBuilder, Layer};
use tower_layer::Layer;

// A simple core service that returns a greeting
struct Greeter;

#[derive(Clone)]
struct GreetRequest(String);

#[derive(Clone)]
struct GreetResponse(String);

impl Service<GreetRequest> for Greeter {
    type Response = GreetResponse;
    type Error = String;
    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>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: GreetRequest) -> Self::Future {
        let name = req.0;
        Box::pin(async move {
            Ok(GreetResponse(format!("Hello, {}!", name)))
        })
    }
}

// A custom layer to log requests
struct LoggingLayer;

impl<S> Layer<S> for LoggingLayer {
    type Service = LoggingService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        LoggingService { inner }
    }
}

struct LoggingService<S> {
    inner: S,
}

impl<S, Req> Service<Req> for LoggingService<S>
where
    S: Service<Req>,
{
    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: Req) -> Self::Future {
        println!("Processing request...");
        let fut = self.inner.call(req);
        Box::pin(async move {
            let res = fut.await;
            println!("Request completed.");
            res
        })
    }
}

#[tokio::main]
async fn main() {
    let greeter = Greeter;

    // Compose the service using ServiceBuilder
    let service = ServiceBuilder::new()
        .layer(LoggingLayer)
        .timeout(Duration::from_secs(5))
        .service(greeter);

    let mut service = service;
    
    // Call the composed service
    let response = service
        .ready()
        .await
        .unwrap()
        .call(GreetRequest("Rustacean".to_string()))
        .await
        .unwrap();

    println!("Response: {}", response.0);
}

In this setup, ServiceBuilder chains the LoggingLayer and the built-in timeout layer around the Greeter service. When you call the resulting service, the request flows through the layers in reverse order (outermost first) and the response flows back through them. This pattern allows you to swap out layers or reorder them without touching the core logic, making it ideal for building complex, production-grade async applications.