A serverless end-to-end architecture can be a great choice when a digital product needs burst scalability, self-healing, and extremely low costs. The biggest downside to going serverless is the lack of in-memory storage which forces every interaction in the API to be completely stateless.
Apollo Server's shortcomings
Apollo Server does not support websocket subscriptions on lambda because it cannot control the configuration of the router. The stateless nature of a lambda also makes it impossible to hold a websocket connection for longer than the duration of the Lambda (or the VM) even if you allow websockets to connect.
We need something stateful with a pay-per-use model to maintain the serverless paradigm and we need to configure the router to work with that stateful service to push messages to Apollo for resolution.
There are many solutions with support for event streams like MongoDB, DynamoDB, Kinesis Streams, Redis, and Postgres. We chose DynamoDB streams because it has a pay-per-use mode that allows us to stick to the serverless paradigm, an easy way to trigger our lambda controller when an event occurs, AND a working open source subscription controller we can leverage bringing it all together.
How does it work?
When a client connects to the router via WebSocket, the router triggers a Lambda that writes a connection ID (provided by the router) to DynamoDB for tracking.
Through the Websocket, Apollo Client sends an operation that triggers another Lambda to register the connection to the event stream.
The subscription controller exposes two important functions that will be used throughout the codebase “publish” and “subscribe.” Whenever the publish function is called for a particular topic, the subscribe function will run. In this case, when we call the publish function, we are sending and storing the payload in the publish function to the DynamoDB stream which in turn triggers a Lambda for every subscribed connection. This is a special Lambda that internally uses the routers API to push the resulting data up the Websocket stream.
Once a Websocket disconnects, the router triggers another Lambda that removes the connection from DynamoDB.
All in all, you end up requiring three Lambdas, one Websocket router, and one durable event stream to be able to build serverless subscriptions where one Lambda deals with simple requests, one deals with resolving router events, and the last one deals with resolving stream events.
You can find a diagram from the library that contains the logic for the router and event stream Lambdas here.
How did we make this easier for everyone?
We built Umble and Umble-Apollo-Server, two packages that automatically build everything for you so you can keep using an enhanced version of Apollo Server with the same developer experience you expect from MDG and without having to touch any of AWS's internals (3-days of work turned into literal minutes).
- Poor documentation on infrastructure needs
- Broken cloudformation file with 159 lines of messy "code"
- Poor wiring on Apollo Server
- Simple Apollo Server configuration (one instance, many handlers) with simple local subscriptions support