Skip to content

RFC: Provide a middleware factory method for event_handler components #1566

Closed as not planned
@walmsles

Description

@walmsles

Is this related to an existing feature request or issue?

#1547

Which AWS Lambda Powertools utility does this relate to?

Event Handler - REST API

Summary

AWS Lambda Powertools provides the Middleware factory which is great for creating middleware for Lambda Event handlers which has been the focus of middleware since inception of the python powertools library. With the API style Event Handlers based from BaseRouter we have provided a way for customers to create larger, more complex monolithic style APIs. This can cause code to become more complex and harder to maintain due to cross-cutting concerns that have an effect on multiple routes.

Use case

Enable middleware to run before/after a route to enable custom data to be injected to the route in a named parameter. being wrapped up as middleware will enable re-use simply and easily for other routes.

Today this is not possible "cleanly" due to the Lambda Event and COntext being locked away within the Router class and not exposed to the route functions at all (which makes complete sense).

To add-in Middleware would require something like the following today to enable passing of custom "user_data" into the route function handler. In the below code the middleware cannot be self contained in nature and would need to access the global "app" instance to access the original Lambda Event or Context which is not ideal for middleware implementations

from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools import Logger
import functools

app = APIGatewayRestResolver()
logger = Logger()

def route_decorator(route_func):
    """Define route decorator middleware function to add my user data"""
    @functools.wraps(route_func)
    def wrapper(*args, **kwargs):
        kwargs["user_data"] = {"param1":"value1"}
        logger.info("calling route_decorator")
        return route_func(*args, **kwargs)
    
    return wrapper

@app.get("/hello")
@route_decorator
def hello_world(user_data=None):
    return {"message": "Hello World", "user_data": user_data}

@logger.inject_lambda_context(log_event=True)
def lambda_handler(event, context):
    return app.resolve(event, context)

Proposal

Provide an event handler middleware factory implementation to simplify setup of route middleware and provide access to key data within the middleware parameters: event, context, router to reduce boilerplate and provide a simpler mechanism for code re-use for cross-cutting concerns.

from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.middleware_factory import router_lambda_decorator 
from aws_lambda_powertools import Logger

app = APIGatewayRestResolver()
logger = Logger()

@router_lambda_decorator
def route_decorator(handler, router):
    """Define route decorator middleware function to add my user data"""
    user_data = call_some_func(router.current_event)
    response = handler(user_data)    
    return call_some_response_wrapper(response)

@app.get("/hello")
@route_decorator
def hello_world(user_data=None):
    return {"message": "Hello World", "user_data": user_data}

@app.get("/foo")
@route_decorator
def get_foo(user_data=None):
    return {"message": "More Foo, Less Bar", "user_data": user_data}

@logger.inject_lambda_context(log_event=True)
def lambda_handler(event, context):
    return app.resolve(event, context)

Out of scope

...

Potential challenges

The main challenge with Middleware here is the fact that the routing middleware is interacting with the actual Resolver class and adding data to it for routing purposes which is a little different than the lambda_handler_decorator factory method so care is required in putting this solution together so that the call chaining makes sense and middleware function itself can be constructed in the same way all other python middleware functions are built.

Now that I've written this I feel it is less of a concern given the "app.resolve()" simply ends up calling the function and likely that is where the change for this feature actually is.

Dependencies and Integrations

No response

Alternative solutions

All other alternative solutions end up being a little "hacky" in nature. I thought the same effect could be achieved using lambda_handler_decorator instead but the trouble is that fact the "app.resolve()" router function does not enable passing of data into it causing any central lambda handler mechanism requiring storing of globals or manipulating the actual event/context objects themselves which is less than ideal.

Middleware with correctly injected data dependencies is always nicer allowing self contained processing for the route handlers so this feature will no longer require global variables to be setup to pollute the lambda execution environment and potentially cause side effects between function invocations which is possible.

Acknowledgment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Closed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions