Skip to content

Feature request: Middleware Chain API for Powertools Utilities #2202

@phipag

Description

@phipag

Use case

Currently, Powertools for AWS Lambda (Java) provides individual utilities like Logging, Tracing, Metrics, and other features through AspectJ annotations and static methods. While effective, this approach lacks a unified and modern functional interface for applying multiple Powertools utilities to Lambda handlers without relying on AspectJ annotations.

Developers need a solution that offers:

  • A unified functional API to compose multiple Powertools features (logging, tracing, metrics, validation, etc.)
  • Type-safe middleware composition that works across all Lambda handler types (RequestHandler, RequestStreamHandler, etc.)
  • Clean separation between business logic and cross-cutting concerns without AspectJ dependencies
  • Flexible composition allowing different orders and combinations of middlewares
  • Modern Java functional programming patterns that feel idiomatic to Java developers

This would provide an alternative to the current AspectJ annotation-based approach while maintaining the same powerful utility features.

Solution/User Experience

Introduce a middleware chain API that allows functional composition of Powertools features. The design below suggests an implementation of the Chain of Responsibility software design pattern:

@FunctionalInterface
public interface Middleware<T, R> {
    R apply(T input, Context context, BiFunction<T, Context, R> next);
}

public class MiddlewareChain<T, R> {
    public MiddlewareChain<T, R> use(Middleware<T, R> middleware) { /* ... */ }
    public R execute(T input, Context context, BiFunction<T, Context, R> handler) { /* ... */ }
}

public class PowertoolsMiddlewares {
    public static <T, R> Middleware<T, R> logging() { /* ... */ }
    public static <T, R> Middleware<T, R> tracing(String serviceName) { /* ... */ }
    public static <T, R> Middleware<T, R> metrics(String namespace) { /* ... */ }
}

Usage example:

public class OrderHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    
    private final MiddlewareChain<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> chain = 
        new MiddlewareChain<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>()
            .use(PowertoolsMiddlewares.logging())
            .use(PowertoolsMiddlewares.tracing("OrderService"))
            .use(PowertoolsMiddlewares.metrics("Orders"));
    
    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
        return chain.execute(input, context, this::processOrder);
    }
    
    private APIGatewayProxyResponseEvent processOrder(APIGatewayProxyRequestEvent request, Context context) {
        // Business logic here
        return APIGatewayProxyResponseEvent.builder()
            .withStatusCode(200)
            .withBody("{\"message\": \"Order processed\"}")
            .build();
    }
}

Benefits:

  • Type-safe: Works with any Lambda handler type through generics
  • Composable: Middlewares can be combined in any order
  • Functional: Leverages modern Java functional programming patterns
  • Consistent: Same API works for RequestHandler, RequestStreamHandler, etc.
  • Flexible: Easy to add custom middlewares or modify the chain

Alternative solutions

Decorator pattern: Wrap handlers with individual decorators

  • Pros: Object-oriented approach, familiar pattern
  • Cons: More verbose, harder to compose multiple decorators, requires creating wrapper classes for each feature

Aspect-Oriented Programming (AOP): Frameworks like Spring AOP are too heavy for Lambda environments and introduce additional dependencies that may not be suitable for serverless architectures.

Acknowledgment


Disclaimer: After creating an issue, please wait until it is triaged and confirmed by a maintainer before implementing it. This will reduce amount of rework and the chance that a pull request gets rejected.

Future readers: Please react with 👍 and your use case to help us understand customer demand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Ideas

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions