powertools-lambda-java icon indicating copy to clipboard operation
powertools-lambda-java copied to clipboard

Feature request: Middleware Chain API for Powertools Utilities

Open phipag opened this issue 2 months ago • 5 comments

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.

phipag avatar Oct 17 '25 08:10 phipag

To be able to support this, we need to add a non-AspectJ interface to all utilities which don't have one yet. I include some sub-issues in this issue.

phipag avatar Oct 20 '25 14:10 phipag

All sub-issues are created now. I will work on them soon.

phipag avatar Oct 21 '25 09:10 phipag

This feature request goes back to an old issue #1170

phipag avatar Oct 27 '25 13:10 phipag

Using AssertJ adds complexity to the build process, as well as a third-party dependency and potential security risks. Therefore, removing AssertJ from Powertools is the highest priority.

Using the built-in Java functionality instead is preferable.

The approach implemented in #2205 uses a Java interface with static methods, providing straightforward integration of individual utilities.

I see the middleware approach as a convenient wrapper/facade around the composable interfaces representing the individual utilities.

However: the term "middleware" is problematic in Java context. It is a placeholder for "application servers" which has an opposite meaning in this case.

I would therefore rename the MiddlewareChain class to PowerToolChain and PowertoolsMiddlewares to PowerTools:

  new PowerToolChain<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>()
            .use(PowerTools.logging())
            .use(PowerTools.tracing("OrderService"))
            .use(PowerTools.metrics("Orders"));

AdamBien avatar Oct 30 '25 06:10 AdamBien

All sub-issues are resolved. This means, this issue is now unblocked.

phipag avatar Nov 13 '25 12:11 phipag