Assume a browser based (or desktop electron) stand alone, client side only application. Multiple users are working with that application at the same time, on their own laptops and at (possibly very) different locations, possibly across the globe. It is conceivable that they are working on a common object. A document, diagram, gameboard, technology radar, whiteboard etc. If that is the case, it would be useful if not necessary for the content to be synchronized across the browser environments.
With regular web applications, backed by a dedicated server side stack, this is fairly easily achieved. WebSockets or SSE can broadcast changes to all clients and AJAX requests are used to send updates from each client to the mighty backend. Google Docs, Office365, MS Teams and Miro are just some examples of this.
However, what if the application runs locally, as a stand alone application, loaded as static application into the local browser. How could different instances of this application – that do not share a common backend – synchronize their context?
In this article I will share what started out as a thought experiment and evolved into a working prototype of a very light weight approach to synchronize application context across users distributed even globally. This approach uses nothing more than serverless functions – cheap (free for small scale environments), light weight and extremely simple to set up.
The approach at its simplest with only two clients can be visualized like this:
User A runs the application locally. From their local browser, a simple HTTP request is sent (1) that is handled by an API Gateway that passes it on to a serverless function. The function generates a new session key and returns that key in the response 2). This key is what binds the users and the common session context or state.
The user has to share the session key (step 3) with the other users who want and are allowed to join the session. Each user has to supply that key to the application. Using the session key, user’s A local application instance can set the actual application context (4). This state is received by the serverless function and held in a cache, identified by the session key. Note: whether this cache is in memory, in a database or in a file storage is not relevant and this moment; this implementation choice can be made and revised at any moment, depending primarily on non-functional requirements such as the size of the session context, the number of session participants, the frequency of update and especially the required robustness and session duration.
The second user’s application instance now joins the session (5) using the session key and requests the current state. From this point onwards, both application instances can post updated session context [to the serverless function] and poll for the latest session context state (for example step 6).
A more advanced implementation would perhaps include some form of change detection, version control and fine grained change conflict handling to not allow inadvertent overriding session context changes. And security provision to ensure not just anyone can guess the session key and join the session. However, let’s focus first on a basic implementation.
Sharing State across the Clouds
What are the basic elements:
- Serverless function to handle
- POST new session request, return new session key
- PUT state for session
- GET state for session
- API Gateway to expose the serverless function and route HTTP requests to it
- static HTML application that
- send HTTP request for new session [key]
- allows the user to enter a session key
- periodically polls for the current session state (and process that state)
- sends the session context after local updates to the state have been made
The initial implementation of the function could be created locally and once it proves itself as mediator between multiple local sessions, we can deploy it cloud side, expose it through the API gateway and try it out across the cloud. The initial implementation will rely on global (memory) context for the function and not use a stand alone cache, database or file store.
In this quick demo, we start in client one and start a new multi client session. This produces a session identifier that we can subsequently use in all other clients that want to share in that same session. Changes in one client pushed to the session context are made available to all other clients- they are polled and applied and it feels as if they are broadcast. The other clients can also make changes to the session context and have those distributed to all associated multi clients.
1. Client 1 Start Multi Client session
2. Client 1 – Multi Client Session is established
3. Client 2 – join multi client session by entering the session key
4. Client 2 – has joined in the session
5. Client 1 – enter message that is added to the multi client session context
6. Client 1 – message was added to session context and the context has been pushed to the serverless multi client session cache
7. Client 2 – has received the updated session context (my opening move) and has added to the session context
8. Client 1 – has received the session context updates made by client 2
You may be underwhelmed by these visuals. They however tell a fairly powerful story. Two application instances, running independently of one another, on different pieces of hardware, possibly even different continents, keep each other informed – allowing their users to collaborate in real time. And there is not a stateful piece of machinery backing these application instances. The two applications are client side browser based applications that use outgoing HTTP requests to synchronize application state.
I will soon publish an article with a fully functional code sample.
My article exploring creating a DIY cache solution with OCI Serverless Functions: https://medium.com/oracledevs/somewhat-stateful-serverless-functions-on-oracle-cloud-infrastructure-implementing-a-diy-cache-433af41ce28d