Skip to content

Commit 67ef278

Browse files
docs(appsync_events): improve AppSync events documentation (#6572)
* Improve appSync events documrntation * Improve appSync events documrntation * Improve appSync events documrntation
1 parent dfed319 commit 67ef278

10 files changed

+203
-33
lines changed

.cfnlintrc.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore_templates:
2+
- examples/event_handler_appsync_events/sam/getting_started_with_appsync_events.yaml

docs/core/event_handler/appsync_events.md

+30-23
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ stateDiagram-v2
4141
* Easily handle publish and subscribe events with dedicated handler methods
4242
* Automatic routing based on namespace and channel patterns
4343
* Support for wildcard patterns to create catch-all handlers
44-
* Process events in parallel corontrol aggregation for batch processing
44+
* Support for async functions
45+
* Aggregation for batch processing
4546
* Graceful error handling for individual events
4647

4748
## Terminology
@@ -59,6 +60,12 @@ It handles connection management, message broadcasting, authentication, and moni
5960

6061
You must have an existing AppSync Events API with real-time capabilities enabled and IAM permissions to invoke your Lambda function. That said, there are no additional permissions required to use Event Handler as routing requires no dependency (_standard library_).
6162

63+
=== "getting_started_with_appsync_events.yaml"
64+
65+
```yaml
66+
--8<-- "examples/event_handler_appsync_events/sam/getting_started_with_appsync_events.yaml"
67+
```
68+
6269
### AppSync request and response format
6370

6471
AppSync Events uses a specific event format for Lambda requests and responses. In most scenarios, Powertools for AWS simplifies this interaction by automatically formatting resolver returns to match the expected AppSync response structure.
@@ -102,12 +109,16 @@ When processing events with Lambda, you can return errors to AppSync in three wa
102109

103110
You can define your handlers for different event types using the `app.on_publish()`, `app.async_on_publish()`, and `app.on_subscribe()` methods.
104111

112+
By default, the resolver processes messages individually. For batch processing, see the [Aggregated Processing](#aggregated-processing) section.
113+
105114
=== "getting_started_with_publish_events.py"
106115

107116
```python hl_lines="5 10 13"
108117
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_publish_events.py"
109118
```
110119

120+
1. The `payload` argument is mandatory and will be passed as a dictionary.
121+
111122
=== "getting_started_with_subscribe_events.py"
112123

113124
```python hl_lines="6 7 13 17"
@@ -159,6 +170,8 @@ You can enable this with the `aggregate` parameter:
159170
--8<-- "examples/event_handler_appsync_events/src/working_with_aggregated_events.py"
160171
```
161172

173+
1. The `payload` argument is mandatory and will be passed as a list of dictionary.
174+
162175
### Handling errors
163176

164177
You can filter or reject events by raising exceptions in your resolvers or by formatting the payload according to the expected response structure. This instructs AppSync not to propagate that specific message, so subscribers will not receive it.
@@ -191,22 +204,22 @@ When processing batch of items with `aggregate=True`, you must format the payloa
191204

192205
=== "working_with_error_handling_response.json"
193206

194-
```python hl_lines="4"
207+
```json hl_lines="4"
195208
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
196209
```
197210

198211
If instead you want to fail the entire batch, you can throw an exception. This will cause the Event Handler to return an error response to AppSync and fail the entire batch.
199212

200-
=== "working_with_error_handling_multiple.py"
213+
=== "fail_entire_batch.py"
201214

202-
```python hl_lines="5 6 13"
203-
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py"
215+
```python hl_lines="6 15 19 30"
216+
--8<-- "examples/event_handler_appsync_events/src/fail_entire_batch.py"
204217
```
205218

206-
=== "working_with_error_handling_response.json"
219+
=== "fail_entire_batch_response.json"
207220

208-
```python hl_lines="5 6 13"
209-
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
221+
```json
222+
--8<-- "examples/event_handler_appsync_events/src/fail_entire_batch_response.json"
210223
```
211224

212225
#### Authorization control
@@ -218,16 +231,10 @@ You can also do content based authorization for channel by raising the `Unauthor
218231
* **When working with publish events** Powertools for AWS stop processing messages and subscribers will not receive any message.
219232
* **When working with subscribe events** the subscription won't be established.
220233

221-
=== "working_with_error_handling.py"
234+
=== "working_with_authorization_control.py"
222235

223-
```python hl_lines="5 6 13"
224-
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling.py"
225-
```
226-
227-
=== "working_with_error_handling_response.json"
228-
229-
```python hl_lines="5 6 13"
230-
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
236+
```python hl_lines="6 21 31"
237+
--8<-- "examples/event_handler_appsync_events/src/working_with_authorization_control.py"
231238
```
232239

233240
### Processing events with async resolvers
@@ -241,7 +248,7 @@ We use `asyncio` module to support async functions, and we ensure reliable execu
241248

242249
=== "working_with_async_resolvers.py"
243250

244-
```python hl_lines="5 6 13"
251+
```python hl_lines="6 14"
245252
--8<-- "examples/event_handler_appsync_events/src/working_with_async_resolvers.py"
246253
```
247254

@@ -251,7 +258,7 @@ You can access to the original Lambda event or context for additional informatio
251258

252259
=== "accessing_event_and_context.py"
253260

254-
```python hl_lines="5 6 13"
261+
```python hl_lines="17"
255262
--8<-- "examples/event_handler_appsync_events/src/accessing_event_and_context.py"
256263
```
257264

@@ -363,26 +370,26 @@ You can test your event handlers by passing a mocked or actual AppSync Events La
363370

364371
=== "getting_started_with_testing_publish.py"
365372

366-
```python hl_lines="5 6 13"
373+
```python
367374
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py"
368375
```
369376

370377
=== "getting_started_with_testing_publish_event.json"
371378

372-
```python hl_lines="5 6 13"
379+
```json
373380
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json"
374381
```
375382

376383
### Testing subscribe events
377384

378385
=== "getting_started_with_testing_subscribe.py"
379386

380-
```python hl_lines="5 6 13"
387+
```python
381388
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py"
382389
```
383390

384391
=== "getting_started_with_testing_subscribe_event.json"
385392

386-
```python hl_lines="5 6 13"
393+
```json
387394
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json"
388395
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
4+
Metadata:
5+
cfn-lint:
6+
ignore_checks:
7+
- E3002
8+
9+
Globals:
10+
Function:
11+
Timeout: 5
12+
MemorySize: 256
13+
Runtime: python3.13
14+
Tracing: Active
15+
Environment:
16+
Variables:
17+
POWERTOOLS_LOG_LEVEL: INFO
18+
POWERTOOLS_SERVICE_NAME: hello
19+
20+
Resources:
21+
HelloWorldFunction:
22+
Type: AWS::Serverless::Function
23+
Properties:
24+
Handler: index.handler
25+
CodeUri: hello_world
26+
27+
WebsocketAPI:
28+
Type: AWS::AppSync::Api
29+
Properties:
30+
EventConfig:
31+
AuthProviders:
32+
- AuthType: API_KEY
33+
ConnectionAuthModes:
34+
- AuthType: API_KEY
35+
DefaultPublishAuthModes:
36+
- AuthType: API_KEY
37+
DefaultSubscribeAuthModes:
38+
- AuthType: API_KEY
39+
Name: RealTimeEventAPI
40+
41+
NameSpaceDataSource:
42+
Type: AWS::AppSync::DataSource
43+
Properties:
44+
ApiId: !GetAtt WebsocketAPI.ApiId
45+
LambdaConfig:
46+
LambdaFunctionArn: !GetAtt HelloWorldFunction.Arn
47+
Name: powertools_lambda
48+
ServiceRoleArn: !GetAtt DataSourceIAMRole.Arn
49+
Type: AWS_LAMBDA
50+
51+
WebsocketApiKey:
52+
Type: AWS::AppSync::ApiKey
53+
Properties:
54+
ApiId: !GetAtt WebsocketAPI.ApiId
55+
56+
WebsocketAPINamespace:
57+
Type: AWS::AppSync::ChannelNamespace
58+
Properties:
59+
ApiId: !GetAtt WebsocketAPI.ApiId
60+
Name: powertools
61+
HandlerConfigs:
62+
OnPublish:
63+
Behavior: DIRECT
64+
Integration:
65+
DataSourceName: powertools_lambda
66+
LambdaConfig:
67+
InvokeType: REQUEST_RESPONSE
68+
OnSubscribe:
69+
Behavior: DIRECT
70+
Integration:
71+
DataSourceName: powertools_lambda
72+
LambdaConfig:
73+
InvokeType: REQUEST_RESPONSE
74+
75+
DataSourceIAMRole:
76+
Type: AWS::IAM::Role
77+
Properties:
78+
AssumeRolePolicyDocument:
79+
Version: '2012-10-17'
80+
Statement:
81+
- Effect: Allow
82+
Principal:
83+
Service: appsync.amazonaws.com
84+
Action: sts:AssumeRole
85+
Policies:
86+
- PolicyName: LambdaInvokePolicy
87+
PolicyDocument:
88+
Version: '2012-10-17'
89+
Statement:
90+
- Effect: Allow
91+
Action:
92+
- lambda:InvokeFunction
93+
Resource: !GetAtt HelloWorldFunction.Arn

examples/event_handler_appsync_events/src/accessing_event_and_context.py

-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
app = AppSyncEventsResolver()
1212

1313

14-
class ValidationError(Exception):
15-
pass
16-
17-
1814
@app.on_publish("/default/channel1")
1915
def handle_channel1_publish(payload: dict[str, Any]):
2016
# Access the full event and context
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
from aws_lambda_powertools import Logger
6+
from aws_lambda_powertools.event_handler import AppSyncEventsResolver
7+
8+
if TYPE_CHECKING:
9+
from aws_lambda_powertools.utilities.typing import LambdaContext
10+
11+
app = AppSyncEventsResolver()
12+
logger = Logger()
13+
14+
15+
class ChannelException(Exception):
16+
pass
17+
18+
19+
@app.on_publish("/default/*", aggregate=True)
20+
def handle_default_namespace_batch(payload: list[dict[str, Any]]):
21+
results: list = []
22+
23+
# Process all events in the batch together
24+
for event in payload:
25+
try:
26+
# Process each event
27+
results.append({"id": event.get("id"), "payload": {"processed": True, "originalEvent": event}})
28+
except Exception as e:
29+
logger.error("Found and error")
30+
raise ChannelException("An exception occurred") from e
31+
32+
return results
33+
34+
35+
def lambda_handler(event: dict, context: LambdaContext):
36+
return app.resolve(event, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"error": "ChannelException - An exception occurred"
3+
}

examples/event_handler_appsync_events/src/getting_started_with_publish_events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
@app.on_publish("/default/channel")
14-
def handle_channel1_publish(payload: dict[str, Any]):
14+
def handle_channel1_publish(payload: dict[str, Any]): # (1)!
1515
# Process the payload for this specific channel
1616
return {
1717
"processed": True,

examples/event_handler_appsync_events/src/working_with_aggregated_events.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ def marshall(item: dict[str, Any]) -> dict[str, Any]:
2020

2121

2222
@app.on_publish("/default/foo/*", aggregate=True)
23-
async def handle_default_namespace_batch(payload: list[dict[str, Any]]):
23+
async def handle_default_namespace_batch(payload: list[dict[str, Any]]): # (1)!
2424
write_operations: list = []
2525

2626
write_operations.extend({"PutRequest": {"Item": marshall(item)}} for item in payload)
2727

28-
# Executar operação de lote no DynamoDB
2928
if write_operations:
3029
dynamodb.batch_write_item(
3130
RequestItems={

examples/event_handler_appsync_events/src/working_with_async_resolvers.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
from typing import TYPE_CHECKING, Any
55

6-
from aws_lambda_powertools.event_handler import AppSyncEventsResolver # type: ignore[attr-defined]
6+
from aws_lambda_powertools.event_handler import AppSyncEventsResolver
77

88
if TYPE_CHECKING:
99
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -13,8 +13,7 @@
1313

1414
@app.async_on_publish("/default/channel1")
1515
async def handle_channel1_publish(payload: dict[str, Any]):
16-
result = await async_process_data(payload)
17-
return result
16+
return await async_process_data(payload)
1817

1918

2019
async def async_process_data(payload: dict[str, Any]):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
from aws_lambda_powertools.event_handler import AppSyncEventsResolver
6+
from aws_lambda_powertools.event_handler.events_appsync.exceptions import UnauthorizedException
7+
8+
if TYPE_CHECKING:
9+
from aws_lambda_powertools.utilities.typing import LambdaContext
10+
11+
app = AppSyncEventsResolver()
12+
13+
14+
@app.on_publish("/default/foo")
15+
def handle_specific_channel(payload: dict[str, Any]):
16+
return payload
17+
18+
19+
@app.on_publish("/*")
20+
def handle_root_channel(payload: dict[str, Any]):
21+
raise UnauthorizedException("You can only publish to /default/foo")
22+
23+
24+
@app.on_subscribe("/default/foo")
25+
def handle_subscription_specific_channel():
26+
return True
27+
28+
29+
@app.on_subscribe("/*")
30+
def handle_subscription_root_channel():
31+
raise UnauthorizedException("You can only subscribe to /default/foo")
32+
33+
34+
def lambda_handler(event: dict, context: LambdaContext):
35+
return app.resolve(event, context)

0 commit comments

Comments
 (0)