Crate layered

Crate layered 

Source
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 Service trait that transforms inputs into outputs asynchronously. Think of it as async 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 Layer trait 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: Enables Intercept middleware
  • dynamic-service: Enables DynamicService for type erasure
  • tower-service: Enables Tower interoperability via the tower module

Modules§

prelude
Common imports for working with layered services.
towertower-service
Tower interoperability for layered services.

Structs§

DynamicServicedynamic-service
Type-erased wrapper for Service that hides the concrete type.
Execute
Wraps a function or closure as a Service.
Interceptintercept
Middleware for observing and modifying service inputs and outputs.
InterceptLayerintercept
Builder for creating Intercept middleware.

Traits§

DynamicServiceExtdynamic-service
Extension trait for converting services to DynamicService.
Layer
A trait for decorating a Service with middleware.
Service
An async function In → Out that processes inputs.
Stack
Builds a service from a tuple of layers and a root service.