On October 30th, 2024, AWS announced a new feature: AppSync Events. This feature lets developers easily broadcast real-time event data to a few or millions of subscribers using secure and performant Serverless WebSocket APIs. But wait, didn't we already have Serverless web sockets with API Gateway Websocket APIs? Well, yes, but not exactly like this.
In this post, you will learn about AppSync Events. I'll cover its basics and provide my take on its future, use cases, usage, and how it differs from API Gateway Websockets. I'll also provide a working JavaScript GitHub repository so you can play with it yourself.
Table of Contents
What's a WebSocket?
WebSocket is a computer communications protocol, providing a simultaneous two-way communication channel over a single Transmission Control Protocol (TCP) connection - Wikipedia
Web sockets are nothing new. They are used in every social application on your phone and on numerous websites that show live scores and real-time updates. Have you ever wondered how your Chrome tab magically gets updated with new information? No, it's not polling. It's WebSockets. The application backend pushes an update (either a broadcast/unicast/multicast) to your browser's web socket.
While the protocol has its own implementation on top of TCP, it starts its handshake on top of the HTTP protocol and then "upgrades" to the WebSocket protocol, where it does its own thing according to the RFC.
If you want to learn more about the protocol, check out the video below:
Let's move on to AppSync Events.
What is AppSync Events?
AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling. - AWS
First of all, let's get it out of the way. This has nothing to do with GraphQL and AppSync.
It's just bad branding for this new interpretation of Serverless websockets.
Core Concepts & Terminology
AppSync events are interpreted differently on WebSockets, both on the physical protocol level and in serverless management and message interpretation.
To my understanding, according to the documentation, they added another layer of abstraction, an inner sub-protocol (AppSync-y) for managing a new channel entity on top of a single WebSocket. This can be seen from the AWS developer's guide diagram:
Once clients connect to the AppSync endpoint, they can try to subscribe to a channel.
The cool trick here is connecting to multiple channels on the same web socket. It gives the illusion of numerous "pub-sub" topics/channels, but it's all the same abstracted web socket underneath. Nice touch!
Subscription requires authorization and there are five options:
These options cover every standard authorization method. I usually opt for a Lambda function that checks the web application's secure cookie and JWT and additional checks.
The documentation goes into a lot of detail and even provides Lambda function input schema example and sample implementation - impressive!
Returning to the topic of channels, there's the /default channel, but you can add more channels. A channel namespace is composed of a maximum of five segments, offering a high degree of flexibility. Think of it as UI-oriented pub sub-topics.
Fo example, you can create a channel for general user updates, or by categories such as NBA, Football, and so on. The ability to use up to five segments gives you the power to do publish updates about different sports: like /users/nba and /users/football.
In addition, you can subscribe to /users/nba/ to get general NBA news, or you can subscribe to /users/nba/* (wildcard!), which means you get all NBA news from the subsegments like /users/nba/bulls and /users/nba/lakers.
When it comes to publishing, this can be done from both the frontend and backend and requires authorization. You can set a different authorization method per channel and use a Lambda function (a nice touch!). If you have permission to publish messages to these channels, you only need to send an HTTP request to the channel (and the segments) address. You can publish from a Lambda function or Event Bridge (API destination, I assume) turning AppSync Events to an event-driven architecture enabler.
First Impressions and Usage
I followed the console and documentation and created my first Events API. I don't remember the last time I saw such well-made documentation for a new launch. There's support for many authorizers and even code examples for the client side. One glaring missing feature is the CDK support, which is just an L1 one. At the time of writing, there's an open issue for CDK L2. I started with the basics - the not really secure API key authorization.
I didn't use the consoler's pub/sub-editor. I went straight to playing with a web application and tried subscribing to and publishing to a channel. I used the code examples from the console. And it worked.
I had some minor issues because I'm a complete frontend noob, but after some help from my amazing colleague Afik Grinstein and we had a working Vite project.
The complete GitHub repo is found at https://github.com/ran-isenberg/appsync-events-client.
Let's review what we built:
Our client subscribes to the 'default/test' channel and segment. Once subscribed, it publishes events via the publish UI text input section.
The events logs section displays all published messages on '/default/test'.
and yes, it's really a websocket with AppSync protocol inside:
and:
Here's the JS code, pretty simple when using Amplify SDK:
I was able to subscribe to another channel, for example, '/users', and check the wildcard subscription. Yeah, it works, and it's super easy. When dealing with unicast or customer specific channels, you must add custom authorization to check whether the user can subscribe to that tenant/user ID message.
Anyways, honestly, this is impressive, it just works.
If you are interested in the full code and HTML file, it's here.
Let's move to the insights section where things get spicy.
Insights and Tips
In this section, I'll share some insights gained from using the service for several hours, leading to the summary and conclusion—will I use it or not?
Security
I like the flexibility of the authorization options. Five authorization types for the initial connection and extra authorization for channel-specific subscriptions are more than enough.
You can also add a per-channel connection authorization method and fine-tune it for publish and subscription requests with a Lambda of your choice. Here's a handler example.
There's AWS WAF support, which doesn't exist for API Gateway Websockets. It's mandatory since we are exposing an HTTP endpoint for publishing messages.
Check out my blog post here to learn about AWS WAF.
No Connection Tables
There's no need to manage connection tables; it's genuinely Serverless!
If you have no idea why I'm surprised, it's because when using API Gateway WebSockets - when you want to send a message to a user, you need to know its web socket connection ID. This mapping is visible during the handshake process ($connect) when you authenticate the user and allow the connection, meaning you need to save it somewhere for your backend service. DynamoDB is an excellent option for storing this key-value information, but it's a code you need to add, write, maintain, and test. It's not a big deal, but AWS didn't give it to us out of the box. Here's it's abstracted. You don't know the connection ID (and I'm sure there is one); you speak the languages of channels. You don't even know who is connected to the channels; the assumption here is that you don't even care. You want to publish a message to a channel, and whoever is subscribed will get that message. Classic pub-sub.
To sum it up, it's a different approach - channel vs. connection id.
Mass Broadcast is Easy
With API Gateway sockets, you need to iterate the connection table, fetch the relevant connection IDs, and then use the AWS SDK to publish a message. You need to handle errors, retries, throttling, and it gets very challenging when the larger the target audience list gets larger.
With AppSync Events, all you need to do is make ONE API call to an HTTP endpoint with the right authorization—mind-blowing. Simple.
However, you don't have any feedback on which users got or didn't get the message. With API GW web sockets, if you publish to a closed connection ID, you get an exception and know it. Maybe that's unimportant to your use case, but you should be aware of that.
Dear AppSync Events team, I have a suggestion. What if there could be a message queue feature for channels? A feature where specific channels (/users) store data for a predefined time even if users log out. Then, when the channel subscription is restored (/users/<user_id>, the missed messages pop out of the queue.
Tenant/User Isolation Requires Channel Design
Unicast is harder (kind of, but not by much). Let's say I need to send user-specific messages over the websocket and all the customers' general messages, but they are customer-specific and cannot be sent to everybody.
Our channels namespace can look something like this: users subscribe to '/tenant/<tenantid>' and'/users/<userid>'. Make sure you don't user wildcard subscription in this instance, otherwise you'd break tenant and user isolation.
Be advised: Channel namespaces are not infinite. There's a quota of 50, but it's a soft link.
In addition, each namespace has a limitation of 50 characters, so pay attention when you add tenant id or user ids to the play - a standard UUID is 36 characters.
(Over) Flexibility Means Complexity
Designing channels and authorizing users to channels adds complexity. You need to understand how to model them; remember what type of data (personal or not) goes into where because there's no protection here. You need to build it. You need to build the logic into your custom channel authorization. You also need to ensure your front-end clients subscribe to the correct channels. In addition, the more entities you add to the system, the more code changes you need to make - if you add an 'admin' persona, you might need to add a new namespace or different/extra subscription with all their updated authorization handlers.
It's much more complicated than in the API GW WebSockets, where you connect and listen to messages. Sure, I need to build a DynamoDB table that keeps a mapping between the connection ID, the user ID, and the session ID, but that's pretty basic.
Developer Experience
Documentation is good but not perfect. CDK support is severely lacking.
The pub sub-editor is a nice touch for quick testing of a channel and its authorization. I don't expect backend developers to work on the console on a daily basis, though. It's a good option for developers who want to test a new channel and see data from their backend publisher.
The Amplify code examples are good, it got me started very quickly. If you don't use their SDK, you can still use Events API but you will need to add all the AppSync headers in the correct places.
Lastly, you have to use the built-in JavaScript event handler for 'onSubscribe' and 'onPublish' custom channel handlers. You can't bring your own function or dependancies. Not the best experience in my view.
Pricing
As Jeremy Daly mentioned:
the pricing is 3x cheaper than API Gateway WebSockets for connection minutes. $0.08 per million connection minutes for AppSync versus $0.25 per million for API Gateway. Message transfers seem to be the same, but AppSync seems to charge you for every "operation".
There's also a generous free tier.
That's a win in my book for AppSync over API Gateway.
Summary - Done Right or Just Different - Or in Other Words, Will I Use it?
AppSync Events is a new take on Serverless WebSockets. It adds complexity to authorization and channel management but fixes the one major downside of using Api GW web sockets for mass broadcast.
If you don't require mass broadcast and always send user-specific messages, the regular API GW websockets are a solid, battle-tested option. I'll write a blog post about it soon with CDK code examples, too!
The service has a bright future and lots of potential. I highly recommend it for POCs, startups, and other companies that are willing to be early adopters.
However, as an enterprise architect, I must think carefully about it. The developer experience is not great, gov-cloud isn't supported yet and being an early adopter means the service can break or change out of the blue. In addition, judging from the past, for example, Evidently and AppConfig seemed to do the same feature set, but with some differences, and in the end, AppConfig remained, and Evidently didn't. By the way, if you want to learn more about AppConfig for feature flags, check my post here.
But who knows, maybe it's the other way around. Maybe API Gateway web sockets will be deprecated over time. It all depends on AppSync Events adoption and on the service team's improvement of the developer experience and documentation.