Skip to content

Commit 4e8ca0f

Browse files
docs(event_handler): add micro function examples (#3056)
* docs: add micro function examples * add sam template * docs: update example to return correct response code * Add missing . Co-authored-by: Leandro Damascena <[email protected]> Signed-off-by: Tony Sherman <[email protected]> * Update python version in example template Co-authored-by: Leandro Damascena <[email protected]> Signed-off-by: Tony Sherman <[email protected]> * Apply suggestions from code review Co-authored-by: Leandro Damascena <[email protected]> Signed-off-by: Tony Sherman <[email protected]> * additional updates to review suggestions * Apply suggestions from code review Co-authored-by: Leandro Damascena <[email protected]> Signed-off-by: Tony Sherman <[email protected]> --------- Signed-off-by: Tony Sherman <[email protected]> Co-authored-by: Tony Sherman <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent 1d502b6 commit 4e8ca0f

File tree

5 files changed

+219
-2
lines changed

5 files changed

+219
-2
lines changed

docs/core/event_handler/api_gateway.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ A micro function means that your final code artifact will be different to each f
847847
**Benefits**
848848

849849
* **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.
850-
* **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.
850+
* **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.
851851
* **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.
852852

853853
**Downsides**
@@ -859,6 +859,33 @@ your development, building, deployment tooling need to accommodate the distinct
859859
* **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.
860860
* Automated testing, operational and security reviews are essential to stability in either approaches.
861861

862+
**Example**
863+
864+
Consider a simplified micro function structured REST API that has two routes:
865+
866+
* `/users` - an endpoint that will return all users of the application on `GET` requests
867+
* `/users/<id>` - an endpoint that looks up a single users details by ID on `GET` requests
868+
869+
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.).
870+
871+
=== "`/users` Endpoint"
872+
```python
873+
--8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py"
874+
```
875+
876+
=== "`/users/<id>` Endpoint"
877+
```python
878+
--8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py"
879+
```
880+
881+
=== "Micro Function Example SAM Template"
882+
```yaml
883+
--8<-- "examples/event_handler_rest/sam/micro_function_template.yaml"
884+
```
885+
886+
???+ note
887+
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.
888+
862889
## Testing your code
863890

864891
You can test your routes by passing a proxy event request with required params.

docs/utilities/data_classes.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayA
197197
if user.get("isAdmin", False):
198198
policy.allow_all_routes()
199199
else:
200-
policy.allow_route(HttpVerb.GET, "/user-profile")
200+
policy.allow_route(HttpVerb.GET.value, "/user-profile")
201201

202202
return policy.asdict()
203203
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: >
4+
micro-function-example
5+
6+
Globals:
7+
Api:
8+
TracingEnabled: true
9+
Cors: # see CORS section
10+
AllowOrigin: "'https://example.com'"
11+
AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'"
12+
MaxAge: "'300'"
13+
BinaryMediaTypes: # see Binary responses section
14+
- "*~1*" # converts to */* for any binary type
15+
16+
Function:
17+
Timeout: 5
18+
Runtime: python3.11
19+
20+
Resources:
21+
# Lambda Function Solely For /users endpoint
22+
AllUsersFunction:
23+
Type: AWS::Serverless::Function
24+
Properties:
25+
Handler: app.lambda_handler
26+
CodeUri: users
27+
Description: Function for /users endpoint
28+
Architectures:
29+
- x86_64
30+
Tracing: Active
31+
Events:
32+
UsersPath:
33+
Type: Api
34+
Properties:
35+
Path: /users
36+
Method: GET
37+
MemorySize: 128 # Each Lambda Function can have it's own memory configuration
38+
Environment:
39+
Variables:
40+
LOG_LEVEL: INFO
41+
Tags:
42+
LambdaPowertools: python
43+
44+
# Lambda Function Solely For /users/{id} endpoint
45+
UserByIdFunction:
46+
Type: AWS::Serverless::Function
47+
Properties:
48+
Handler: app.lambda_handler
49+
CodeUri: users_by_id
50+
Description: Function for /users/{id} endpoint
51+
Architectures:
52+
- x86_64
53+
Tracing: Active
54+
Events:
55+
UsersByIdPath:
56+
Type: Api
57+
Properties:
58+
Path: /users/{id+}
59+
Method: GET
60+
MemorySize: 128 # Each Lambda Function can have it's own memory configuration
61+
Environment:
62+
Variables:
63+
LOG_LEVEL: INFO
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import json
2+
from dataclasses import dataclass
3+
from http import HTTPStatus
4+
5+
from aws_lambda_powertools import Logger
6+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response
7+
from aws_lambda_powertools.utilities.typing import LambdaContext
8+
9+
logger = Logger()
10+
11+
# This would likely be a db lookup
12+
users = [
13+
{
14+
"user_id": "b0b2a5bf-ee1e-4c5e-9a86-91074052739e",
15+
"email": "[email protected]",
16+
"active": True,
17+
},
18+
{
19+
"user_id": "3a9df6b1-938c-4e80-bd4a-0c966f4b1c1e",
20+
"email": "[email protected]",
21+
"active": False,
22+
},
23+
{
24+
"user_id": "aa0d3d09-9cb9-42b9-9e63-1fb17ea52981",
25+
"email": "[email protected]",
26+
"active": True,
27+
},
28+
]
29+
30+
31+
@dataclass
32+
class User:
33+
user_id: str
34+
email: str
35+
active: bool
36+
37+
38+
app = APIGatewayRestResolver()
39+
40+
41+
@app.get("/users")
42+
def all_active_users():
43+
"""HTTP Response for all active users"""
44+
all_users = [User(**user) for user in users]
45+
all_active_users = [user.__dict__ for user in all_users if user.active]
46+
47+
return Response(
48+
status_code=HTTPStatus.OK.value,
49+
content_type="application/json",
50+
body=json.dumps(all_active_users),
51+
)
52+
53+
54+
@logger.inject_lambda_context()
55+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
56+
return app.resolve(event, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import json
2+
from dataclasses import dataclass
3+
from http import HTTPStatus
4+
5+
from aws_lambda_powertools import Logger
6+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response
7+
from aws_lambda_powertools.utilities.typing import LambdaContext
8+
9+
logger = Logger()
10+
11+
# This would likely be a db lookup
12+
users = [
13+
{
14+
"user_id": "b0b2a5bf-ee1e-4c5e-9a86-91074052739e",
15+
"email": "[email protected]",
16+
"active": True,
17+
},
18+
{
19+
"user_id": "3a9df6b1-938c-4e80-bd4a-0c966f4b1c1e",
20+
"email": "[email protected]",
21+
"active": False,
22+
},
23+
{
24+
"user_id": "aa0d3d09-9cb9-42b9-9e63-1fb17ea52981",
25+
"email": "[email protected]",
26+
"active": True,
27+
},
28+
]
29+
30+
31+
@dataclass
32+
class User:
33+
user_id: str
34+
email: str
35+
active: bool
36+
37+
38+
def get_user_by_id(user_id: str) -> Union[User, None]:
39+
for user_data in users:
40+
if user_data["user_id"] == user_id:
41+
return User(
42+
user_id=str(user_data["user_id"]),
43+
email=str(user_data["email"]),
44+
active=bool(user_data["active"]),
45+
)
46+
47+
return None
48+
49+
50+
app = APIGatewayRestResolver()
51+
52+
53+
@app.get("/users/<user_id>")
54+
def all_active_users(user_id: str):
55+
"""HTTP Response for all active users"""
56+
user = get_user_by_id(user_id)
57+
58+
if user:
59+
return Response(
60+
status_code=HTTPStatus.OK.value,
61+
content_type="application/json",
62+
body=json.dumps(user.__dict__),
63+
)
64+
65+
else:
66+
return Response(status_code=HTTPStatus.NOT_FOUND)
67+
68+
69+
@logger.inject_lambda_context()
70+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
71+
return app.resolve(event, context)

0 commit comments

Comments
 (0)