Skip to content

Commit a1bd8bf

Browse files
docs(event_handler): add docs for AppSync event resolver (#6557)
AppSync Events
1 parent 6a0282a commit a1bd8bf

19 files changed

+928
-0
lines changed
+388
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
---
2+
title: AppSync Events
3+
description: Core utility
4+
status: new
5+
---
6+
7+
Event Handler for AWS AppSync real-time events.
8+
9+
```mermaid
10+
stateDiagram-v2
11+
direction LR
12+
EventSource: AppSync Events
13+
EventHandlerResolvers: Publish & Subscribe events
14+
LambdaInit: Lambda invocation
15+
EventHandler: Event Handler
16+
EventHandlerResolver: Route event based on namespace/channel
17+
YourLogic: Run your registered handler function
18+
EventHandlerResolverBuilder: Adapts response to AppSync contract
19+
LambdaResponse: Lambda response
20+
21+
state EventSource {
22+
EventHandlerResolvers
23+
}
24+
25+
EventHandlerResolvers --> LambdaInit
26+
27+
LambdaInit --> EventHandler
28+
EventHandler --> EventHandlerResolver
29+
30+
state EventHandler {
31+
[*] --> EventHandlerResolver: app.resolve(event, context)
32+
EventHandlerResolver --> YourLogic
33+
YourLogic --> EventHandlerResolverBuilder
34+
}
35+
36+
EventHandler --> LambdaResponse
37+
```
38+
39+
## Key Features
40+
41+
* Easily handle publish and subscribe events with dedicated handler methods
42+
* Automatic routing based on namespace and channel patterns
43+
* Support for wildcard patterns to create catch-all handlers
44+
* Process events in parallel corontrol aggregation for batch processing
45+
* Graceful error handling for individual events
46+
47+
## Terminology
48+
49+
**[AWS AppSync Events](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html){target="_blank"}**. A service that enables you to quickly build secure, scalable real-time WebSocket APIs without managing infrastructure or writing API code.
50+
51+
It handles connection management, message broadcasting, authentication, and monitoring, reducing time to market and operational costs.
52+
53+
## Getting started
54+
55+
???+ tip "Tip: New to AppSync Real-time API?"
56+
Visit [AWS AppSync Real-time documentation](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-getting-started.html){target="_blank"} to understand how to set up subscriptions and pub/sub messaging.
57+
58+
### Required resources
59+
60+
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_).
61+
62+
### AppSync request and response format
63+
64+
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.
65+
66+
=== "payload_request.json"
67+
68+
```python hl_lines="5 10 12"
69+
--8<-- "examples/event_handler_appsync_events/src/payload_request.json"
70+
```
71+
72+
=== "payload_response.json"
73+
74+
```python hl_lines="5 10 12"
75+
--8<-- "examples/event_handler_appsync_events/src/payload_response.json"
76+
```
77+
78+
=== "payload_response_with_error.json"
79+
80+
```python hl_lines="5 10 12"
81+
--8<-- "examples/event_handler_appsync_events/src/payload_response_with_error.json"
82+
```
83+
84+
=== "payload_response_fail_request.json"
85+
86+
```python hl_lines="5 10 12"
87+
--8<-- "examples/event_handler_appsync_events/src/payload_response_fail_request.json"
88+
```
89+
90+
#### Events response with error
91+
92+
When processing events with Lambda, you can return errors to AppSync in three ways:
93+
94+
* **Item specific error:** Return an `error` key within each individual item's response. AppSync Events expects this format for item-specific errors.
95+
* **Fail entire request:** Return a JSON object with a top-level `error` key. This signals a general failure, and AppSync treats the entire request as unsuccessful.
96+
* **Unauthorized exception**: Raise the **UnauthorizedException** exception to reject a subscribe or publish request with HTTP 403.
97+
98+
### Resolver decorator
99+
100+
???+ important
101+
The event handler automatically parses the incoming event data and invokes the appropriate handler based on the namespace/channel pattern you register.
102+
103+
You can define your handlers for different event types using the `app.on_publish()`, `app.async_on_publish()`, and `app.on_subscribe()` methods.
104+
105+
=== "getting_started_with_publish_events.py"
106+
107+
```python hl_lines="5 10 12"
108+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_publish_events.py"
109+
```
110+
111+
=== "getting_started_with_subscribe_events.py"
112+
113+
```python hl_lines="5 6 13"
114+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_subscribe_events.py"
115+
```
116+
117+
## Advanced
118+
119+
### Wildcard patterns and handler precedence
120+
121+
You can use wildcard patterns to create catch-all handlers for multiple channels or namespaces. This is particularly useful for centralizing logic that applies to multiple channels.
122+
123+
When an event matches with multiple handlers, the most specific pattern takes precedence.
124+
125+
???+ note "Supported wildcard patterns"
126+
Only the following patterns are supported:
127+
128+
* `/namespace/*` - Matches all channels in the specified namespace
129+
* `/*` - Matches all channels in all namespaces
130+
131+
Patterns like `/namespace/channel*` or `/namespace/*/subpath` are not supported.
132+
133+
More specific routes will always take precedence over less specific ones. For example, `/default/channel1` will take precedence over `/default/*`, which will take precedence over `/*`.
134+
135+
=== "working_with_wildcard_resolvers.py"
136+
137+
```python hl_lines="5 6 13"
138+
--8<-- "examples/event_handler_appsync_events/src/working_with_wildcard_resolvers.py"
139+
```
140+
141+
If the event doesn't match any registered handler, the Event Handler will log a warning and skip processing the event.
142+
143+
### Aggregated processing
144+
145+
???+ note "Aggregate Processing"
146+
When `aggregate=True`, your handler receives a list of all events, requiring you to manage the response format. Ensure your response includes results for each event in the expected [AppSync Request and Response Format](#appsync-request-and-response-format).
147+
148+
In some scenarios, you might want to process all events for a channel as a batch rather than individually. This is useful when you need to:
149+
150+
* Optimize database operations by making a single batch query
151+
* Ensure all events are processed together or not at all
152+
* Apply custom error handling logic for the entire batch
153+
154+
You can enable this with the `aggregate` parameter:
155+
156+
=== "working_with_aggregated_events.py"
157+
158+
```python hl_lines="5 6 13"
159+
--8<-- "examples/event_handler_appsync_events/src/working_with_aggregated_events.py"
160+
```
161+
162+
### Handling errors
163+
164+
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.
165+
166+
#### Handling errors with individual items
167+
168+
When processing items individually with `aggregate=False`, you can raise an exception to fail a specific message. When this happens, the Event Handler will catch it and include the exception name and message in the response.
169+
170+
=== "working_with_error_handling.py"
171+
172+
```python hl_lines="5 6 13"
173+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling.py"
174+
```
175+
176+
=== "working_with_error_handling_response.json"
177+
178+
```python hl_lines="5 6 13"
179+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
180+
```
181+
182+
#### Handling errors with batch of items
183+
184+
When processing batch of items with `aggregate=True`, you must format the payload according the expected response.
185+
186+
=== "working_with_error_handling_multiple.py"
187+
188+
```python hl_lines="5 6 13"
189+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py"
190+
```
191+
192+
=== "working_with_error_handling_response.json"
193+
194+
```python hl_lines="5 6 13"
195+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
196+
```
197+
198+
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.
199+
200+
=== "working_with_error_handling_multiple.py"
201+
202+
```python hl_lines="5 6 13"
203+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py"
204+
```
205+
206+
=== "working_with_error_handling_response.json"
207+
208+
```python hl_lines="5 6 13"
209+
--8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
210+
```
211+
212+
#### Authorization control
213+
214+
!!! warning "Raising `UnauthorizedException` will cause the Lambda invocation to fail."
215+
216+
You can also do content based authorization for channel by raising the `UnauthorizedException` exception. This can cause two situations:
217+
218+
* **When working with publish events** Powertools for AWS stop processing messages and subscribers will not receive any message.
219+
* **When working with subscribe events** the subscription won't be established.
220+
221+
=== "working_with_error_handling.py"
222+
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"
231+
```
232+
233+
### Processing events with async resolvers
234+
235+
Use the `@app.async_on_publish()` decorator to process events asynchronously.
236+
237+
We use `asyncio` module to support async functions, and we ensure reliable execution by managing the event loop.
238+
239+
???+ note "Events order and AppSync Events"
240+
AppSync does not rely on event order. As long as each event includes the original `id`, AppSync processes them correctly regardless of the order in which they are received.
241+
242+
=== "working_with_async_resolvers.py"
243+
244+
```python hl_lines="5 6 13"
245+
--8<-- "examples/event_handler_appsync_events/src/working_with_async_resolvers.py"
246+
```
247+
248+
### Accessing Lambda context and event
249+
250+
You can access to the original Lambda event or context for additional information. These are accessible via the app instance:
251+
252+
=== "accessing_event_and_context.py"
253+
254+
```python hl_lines="5 6 13"
255+
--8<-- "examples/event_handler_appsync_events/src/accessing_event_and_context.py"
256+
```
257+
258+
## Event Handler workflow
259+
260+
### Working with single items
261+
262+
<center>
263+
```mermaid
264+
sequenceDiagram
265+
participant Client
266+
participant AppSync
267+
participant Lambda
268+
participant EventHandler
269+
note over Client,EventHandler: Individual Event Processing (aggregate=False)
270+
Client->>+AppSync: Send multiple events to channel
271+
AppSync->>+Lambda: Invoke Lambda with batch of events
272+
Lambda->>+EventHandler: Process events with aggregate=False
273+
loop For each event in batch
274+
EventHandler->>EventHandler: Process individual event
275+
end
276+
EventHandler-->>-Lambda: Return array of processed events
277+
Lambda-->>-AppSync: Return event-by-event responses
278+
AppSync-->>-Client: Report individual event statuses
279+
```
280+
</center>
281+
282+
### Working with aggregated items
283+
284+
<center>
285+
```mermaid
286+
sequenceDiagram
287+
participant Client
288+
participant AppSync
289+
participant Lambda
290+
participant EventHandler
291+
note over Client,EventHandler: Aggregate Processing Workflow
292+
Client->>+AppSync: Send multiple events to channel
293+
AppSync->>+Lambda: Invoke Lambda with batch of events
294+
Lambda->>+EventHandler: Process events with aggregate=True
295+
EventHandler->>EventHandler: Batch of events
296+
EventHandler->>EventHandler: Process entire batch at once
297+
EventHandler->>EventHandler: Format response for each event
298+
EventHandler-->>-Lambda: Return aggregated results
299+
Lambda-->>-AppSync: Return success responses
300+
AppSync-->>-Client: Confirm all events processed
301+
```
302+
</center>
303+
304+
### Authorization fails for publish
305+
306+
<center>
307+
```mermaid
308+
sequenceDiagram
309+
participant Client
310+
participant AppSync
311+
participant Lambda
312+
participant EventHandler
313+
note over Client,EventHandler: Publish Event Authorization Flow
314+
Client->>AppSync: Publish message to channel
315+
AppSync->>Lambda: Invoke Lambda with publish event
316+
Lambda->>EventHandler: Process publish event
317+
alt Authorization Failed
318+
EventHandler->>EventHandler: Authorization check fails
319+
EventHandler->>Lambda: Raise UnauthorizedException
320+
Lambda->>AppSync: Return error response
321+
AppSync--xClient: Message not delivered
322+
AppSync--xAppSync: No distribution to subscribers
323+
else Authorization Passed
324+
EventHandler->>Lambda: Return successful response
325+
Lambda->>AppSync: Return processed event
326+
AppSync->>Client: Acknowledge message
327+
AppSync->>AppSync: Distribute to subscribers
328+
end
329+
```
330+
</center>
331+
332+
### Authorization fails for subscribe
333+
334+
<center>
335+
```mermaid
336+
sequenceDiagram
337+
participant Client
338+
participant AppSync
339+
participant Lambda
340+
participant EventHandler
341+
note over Client,EventHandler: Subscribe Event Authorization Flow
342+
Client->>AppSync: Request subscription to channel
343+
AppSync->>Lambda: Invoke Lambda with subscribe event
344+
Lambda->>EventHandler: Process subscribe event
345+
alt Authorization Failed
346+
EventHandler->>EventHandler: Authorization check fails
347+
EventHandler->>Lambda: Raise UnauthorizedException
348+
Lambda->>AppSync: Return error response
349+
AppSync--xClient: Subscription denied (HTTP 403)
350+
else Authorization Passed
351+
EventHandler->>Lambda: Return successful response
352+
Lambda->>AppSync: Return authorization success
353+
AppSync->>Client: Subscription established
354+
end
355+
```
356+
</center>
357+
358+
## Testing your code
359+
360+
You can test your event handlers by passing a mocked or actual AppSync Events Lambda event.
361+
362+
### Testing publish events
363+
364+
=== "getting_started_with_testing_publish.py"
365+
366+
```python hl_lines="5 6 13"
367+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py"
368+
```
369+
370+
=== "getting_started_with_testing_publish_event.json"
371+
372+
```python hl_lines="5 6 13"
373+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json"
374+
```
375+
376+
### Testing subscribe events
377+
378+
=== "getting_started_with_testing_subscribe.py"
379+
380+
```python hl_lines="5 6 13"
381+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py"
382+
```
383+
384+
=== "getting_started_with_testing_subscribe_event.json"
385+
386+
```python hl_lines="5 6 13"
387+
--8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json"
388+
```

0 commit comments

Comments
 (0)