In this lesson, you will learn how to compose asynchronous service pipelines by leveraging generics to create flexible, reusable middleware layers.
The Middleware Concept
In web services, middleware is a layer that wraps a primary handler. It allows you to perform actions—like logging, authentication, or rate limiting—before and after the main request processing. By using generics, we can design middleware that works with any service that satisfies a specific trait, rather than being hardcoded to one specific function.
Implementing a Generic Service
To build a pipeline, we define a trait that represents an asynchronous service. We then create a "wrapper" struct that holds the inner service as a generic parameter S.
use std::future::Future;
// Our service trait
pub trait Service<Request> {
type Response;
type Future: Future<Output = Self::Response>;
fn call(&self, req: Request) -> Self::Future;
}
// A middleware that wraps any generic service S
pub struct LoggingMiddleware<S> {
inner: S,
}
impl<S, Request> Service<Request> for LoggingMiddleware<S>
where
S: Service<Request>,
{
type Response = S::Response;
type Future = S::Future;
fn call(&self, req: Request) -> Self::Future {
println!("Intercepting request...");
self.inner.call(req)
}
}
Why Generics Matter Here
By using the generic bound S: Service<Request>, we achieve static dispatch. The compiler knows exactly which concrete types are in your middleware stack at compile time. This allows the compiler to inline your middleware logic, effectively removing the performance cost of the abstraction.
Because LoggingMiddleware<S> itself implements Service, you can wrap it in another middleware, then another, creating a chain: Auth<Logging<DatabaseHandler>>. Each layer remains oblivious to the others, knowing only that it is communicating with something that fulfills the Service trait. This decouples your concerns, allowing you to swap out or reorder middleware components without touching the underlying business logic.
Key Takeaway: Generics allow you to build type-safe, performant middleware chains by defining wrappers that operate on any type satisfying a core service trait, enabling flexible and modular service composition.