Skip to content

Commit d62b0a0

Browse files
michaelbrewerDanyC97eldritchideenarthurf1969heitorlessa
authored
docs(api-gateway): add support for new router feature (aws-powertools#767)
Co-authored-by: Dani Comnea <[email protected]> Co-authored-by: Steve Cook <[email protected]> Co-authored-by: Arthur Freund <[email protected]> Co-authored-by: heitorlessa <[email protected]>
1 parent e09fe44 commit d62b0a0

File tree

1 file changed

+130
-6
lines changed

1 file changed

+130
-6
lines changed

docs/core/event_handler/api_gateway.md

+130-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balan
1212
* Integrates with [Data classes utilities](../../utilities/data_classes.md){target="_blank"} to easily access event and identity information
1313
* Built-in support for Decimals JSON encoding
1414
* Support for dynamic path expressions
15+
* Router to allow for splitting up the handler accross multiple files
1516

1617
## Getting started
1718

@@ -75,12 +76,11 @@ This is the sample infrastructure for API Gateway we are using for the examples
7576

7677
Outputs:
7778
HelloWorldApigwURL:
78-
Description: "API Gateway endpoint URL for Prod environment for Hello World Function"
79-
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello"
80-
81-
HelloWorldFunction:
82-
Description: "Hello World Lambda Function ARN"
83-
Value: !GetAtt HelloWorldFunction.Arn
79+
Description: "API Gateway endpoint URL for Prod environment for Hello World Function"
80+
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello"
81+
HelloWorldFunction:
82+
Description: "Hello World Lambda Function ARN"
83+
Value: !GetAtt HelloWorldFunction.Arn
8484
```
8585

8686
### API Gateway decorator
@@ -853,6 +853,130 @@ You can instruct API Gateway handler to use a custom serializer to best suit you
853853
}
854854
```
855855

856+
### Split routes with Router
857+
858+
As you grow the number of routes a given Lambda function should handle, it is natural to split routes into separate files to ease maintenance - That's where the `Router` feature is useful.
859+
860+
Let's assume you have `app.py` as your Lambda function entrypoint and routes in `users.py`, this is how you'd use the `Router` feature.
861+
862+
=== "users.py"
863+
864+
We import **Router** instead of **ApiGatewayResolver**; syntax wise is exactly the same.
865+
866+
```python hl_lines="4 8 12 15 21"
867+
import itertools
868+
from typing import Dict
869+
870+
from aws_lambda_powertools import Logger
871+
from aws_lambda_powertools.event_handler.api_gateway import Router
872+
873+
logger = Logger(child=True)
874+
router = Router()
875+
USERS = {"user1": "details_here", "user2": "details_here", "user3": "details_here"}
876+
877+
878+
@router.get("/users")
879+
def get_users() -> Dict:
880+
# /users?limit=1
881+
pagination_limit = router.current_event.get_query_string_value(name="limit", default_value=10)
882+
883+
logger.info(f"Fetching the first {pagination_limit} users...")
884+
ret = dict(itertools.islice(USERS.items(), int(pagination_limit)))
885+
return {"items": [ret]}
886+
887+
@router.get("/users/<username>")
888+
def get_user(username: str) -> Dict:
889+
logger.info(f"Fetching username {username}")
890+
return {"details": USERS.get(username, {})}
891+
892+
# many other related /users routing
893+
```
894+
895+
=== "app.py"
896+
897+
We use `include_router` method and include all user routers registered in the `router` global object.
898+
899+
```python hl_lines="6 8-9"
900+
from typing import Dict
901+
902+
from aws_lambda_powertools.event_handler import ApiGatewayResolver
903+
from aws_lambda_powertools.utilities.typing import LambdaContext
904+
905+
import users
906+
907+
app = ApiGatewayResolver()
908+
app.include_router(users.router)
909+
910+
911+
def lambda_handler(event: Dict, context: LambdaContext):
912+
return app.resolve(event, context)
913+
```
914+
915+
#### Route prefix
916+
917+
In the previous example, `users.py` routes had a `/users` prefix. This might grow over time and become repetitive.
918+
919+
When necessary, you can set a prefix when including a router object. This means you could remove `/users` prefix in `users.py` altogether.
920+
921+
=== "app.py"
922+
923+
```python hl_lines="9"
924+
from typing import Dict
925+
926+
from aws_lambda_powertools.event_handler import ApiGatewayResolver
927+
from aws_lambda_powertools.utilities.typing import LambdaContext
928+
929+
import users
930+
931+
app = ApiGatewayResolver()
932+
app.include_router(users.router, prefix="/users") # prefix '/users' to any route in `users.router`
933+
934+
935+
def lambda_handler(event: Dict, context: LambdaContext):
936+
return app.resolve(event, context)
937+
```
938+
939+
=== "users.py"
940+
941+
```python hl_lines="11 15"
942+
from typing import Dict
943+
944+
from aws_lambda_powertools import Logger
945+
from aws_lambda_powertools.event_handler.api_gateway import Router
946+
947+
logger = Logger(child=True)
948+
router = Router()
949+
USERS = {"user1": "details", "user2": "details", "user3": "details"}
950+
951+
952+
@router.get("/") # /users, when we set the prefix in app.py
953+
def get_users() -> Dict:
954+
...
955+
956+
@router.get("/<username>")
957+
def get_user(username: str) -> Dict:
958+
...
959+
960+
# many other related /users routing
961+
```
962+
963+
964+
#### Trade-offs
965+
966+
!!! tip "TL;DR. Balance your latency requirements, cognitive overload, least privilege, and operational overhead to decide between one, few, or many single purpose functions."
967+
968+
Route splitting feature helps accommodate customers familiar with popular frameworks and practices found in the Python community.
969+
970+
It can help better organize your code and reason
971+
972+
This can also quickly lead to discussions whether it facilitates a monolithic vs single-purpose function. To this end, these are common trade-offs you'll encounter as you grow your Serverless service, specifically synchronous functions.
973+
974+
**Least privilege**. Start with a monolithic function, then split them as their data access & boundaries become clearer. Treat Lambda functions as separate logical resources to more easily scope permissions.
975+
976+
**Package size**. Consider Lambda Layers for third-party dependencies and service-level shared code. Treat third-party dependencies as dev dependencies, and Lambda Layers as a mechanism to speed up build and deployments.
977+
978+
**Cold start**. High load can diminish the benefit of monolithic functions depending on your latency requirements. Always load test to pragmatically balance between your customer experience and development cognitive load.
979+
856980
## Testing your code
857981

858982
You can test your routes by passing a proxy event request where `path` and `httpMethod`.

0 commit comments

Comments
 (0)