diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index fc57e9b1f6..257c17ec13 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -847,7 +847,7 @@ A micro function means that your final code artifact will be different to each f **Benefits** * **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. -* **Discoverability**. Micro functions are easier do visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. +* **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. * **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="_blank"} to optimize builds for external dependencies. **Downsides** @@ -859,6 +859,33 @@ your development, building, deployment tooling need to accommodate the distinct * **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. * Automated testing, operational and security reviews are essential to stability in either approaches. +**Example** + +Consider a simplified micro function structured REST API that has two routes: + +* `/users` - an endpoint that will return all users of the application on `GET` requests +* `/users/` - an endpoint that looks up a single users details by ID on `GET` requests + +Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). + +=== "`/users` Endpoint" + ```python + --8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" + ``` + +=== "`/users/` Endpoint" + ```python + --8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" + ``` + +=== "Micro Function Example SAM Template" + ```yaml + --8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" + ``` + +???+ note + You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a Lambda Layer. + ## Testing your code You can test your routes by passing a proxy event request with required params. diff --git a/docs/utilities/data_classes.md b/docs/utilities/data_classes.md index 6016e68cfe..fd4a176f63 100644 --- a/docs/utilities/data_classes.md +++ b/docs/utilities/data_classes.md @@ -197,7 +197,7 @@ Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayA if user.get("isAdmin", False): policy.allow_all_routes() else: - policy.allow_route(HttpVerb.GET, "/user-profile") + policy.allow_route(HttpVerb.GET.value, "/user-profile") return policy.asdict() ``` diff --git a/examples/event_handler_rest/sam/micro_function_template.yaml b/examples/event_handler_rest/sam/micro_function_template.yaml new file mode 100644 index 0000000000..fb27206fdd --- /dev/null +++ b/examples/event_handler_rest/sam/micro_function_template.yaml @@ -0,0 +1,63 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + micro-function-example + +Globals: + Api: + TracingEnabled: true + Cors: # see CORS section + AllowOrigin: "'https://example.com'" + AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'" + MaxAge: "'300'" + BinaryMediaTypes: # see Binary responses section + - "*~1*" # converts to */* for any binary type + + Function: + Timeout: 5 + Runtime: python3.11 + +Resources: + # Lambda Function Solely For /users endpoint + AllUsersFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.lambda_handler + CodeUri: users + Description: Function for /users endpoint + Architectures: + - x86_64 + Tracing: Active + Events: + UsersPath: + Type: Api + Properties: + Path: /users + Method: GET + MemorySize: 128 # Each Lambda Function can have it's own memory configuration + Environment: + Variables: + LOG_LEVEL: INFO + Tags: + LambdaPowertools: python + + # Lambda Function Solely For /users/{id} endpoint + UserByIdFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.lambda_handler + CodeUri: users_by_id + Description: Function for /users/{id} endpoint + Architectures: + - x86_64 + Tracing: Active + Events: + UsersByIdPath: + Type: Api + Properties: + Path: /users/{id+} + Method: GET + MemorySize: 128 # Each Lambda Function can have it's own memory configuration + Environment: + Variables: + LOG_LEVEL: INFO diff --git a/examples/event_handler_rest/src/micro_function_all_users_route.py b/examples/event_handler_rest/src/micro_function_all_users_route.py new file mode 100644 index 0000000000..1a809634b4 --- /dev/null +++ b/examples/event_handler_rest/src/micro_function_all_users_route.py @@ -0,0 +1,56 @@ +import json +from dataclasses import dataclass +from http import HTTPStatus + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + +# This would likely be a db lookup +users = [ + { + "user_id": "b0b2a5bf-ee1e-4c5e-9a86-91074052739e", + "email": "john.doe@example.com", + "active": True, + }, + { + "user_id": "3a9df6b1-938c-4e80-bd4a-0c966f4b1c1e", + "email": "jane.smith@example.com", + "active": False, + }, + { + "user_id": "aa0d3d09-9cb9-42b9-9e63-1fb17ea52981", + "email": "alex.wilson@example.com", + "active": True, + }, +] + + +@dataclass +class User: + user_id: str + email: str + active: bool + + +app = APIGatewayRestResolver() + + +@app.get("/users") +def all_active_users(): + """HTTP Response for all active users""" + all_users = [User(**user) for user in users] + all_active_users = [user.__dict__ for user in all_users if user.active] + + return Response( + status_code=HTTPStatus.OK.value, + content_type="application/json", + body=json.dumps(all_active_users), + ) + + +@logger.inject_lambda_context() +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_rest/src/micro_function_user_by_id_route.py b/examples/event_handler_rest/src/micro_function_user_by_id_route.py new file mode 100644 index 0000000000..4f6e7add85 --- /dev/null +++ b/examples/event_handler_rest/src/micro_function_user_by_id_route.py @@ -0,0 +1,71 @@ +import json +from dataclasses import dataclass +from http import HTTPStatus + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + +# This would likely be a db lookup +users = [ + { + "user_id": "b0b2a5bf-ee1e-4c5e-9a86-91074052739e", + "email": "john.doe@example.com", + "active": True, + }, + { + "user_id": "3a9df6b1-938c-4e80-bd4a-0c966f4b1c1e", + "email": "jane.smith@example.com", + "active": False, + }, + { + "user_id": "aa0d3d09-9cb9-42b9-9e63-1fb17ea52981", + "email": "alex.wilson@example.com", + "active": True, + }, +] + + +@dataclass +class User: + user_id: str + email: str + active: bool + + +def get_user_by_id(user_id: str) -> Union[User, None]: + for user_data in users: + if user_data["user_id"] == user_id: + return User( + user_id=str(user_data["user_id"]), + email=str(user_data["email"]), + active=bool(user_data["active"]), + ) + + return None + + +app = APIGatewayRestResolver() + + +@app.get("/users/") +def all_active_users(user_id: str): + """HTTP Response for all active users""" + user = get_user_by_id(user_id) + + if user: + return Response( + status_code=HTTPStatus.OK.value, + content_type="application/json", + body=json.dumps(user.__dict__), + ) + + else: + return Response(status_code=HTTPStatus.NOT_FOUND) + + +@logger.inject_lambda_context() +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context)