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.

Sub-issues

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