Eventual consistency is probably the only consistency that one can hope for using Javascript and WebSockets

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

On the one hand, I am very impressed with this article: Eventual Consistency in Real-time Web Apps. On the other hand, how can anyone keep up with what is best practice in the land of Javascript, when every week seems to bring a new framework or methodology?

Having said that, I’ll point out that there is no way to ensure a 1-to-1 match between one’s backend model and one’s front-end model, so all one can do is pick one to be the truth (this will be the backend model unless you are creating peer-to-peer software) and deal with the fact that the front-end model’s that subscribe to that backend will always be just a little bit behind (perhaps 1 or 2 seconds out of date).

The way I usually think about is that there are two parallel tasks going on during page load:

Load the initial data from the backend once the page is loaded.
Subscribe to web socket notifications.
Looking at task one (loading the data from the backend), everything should look familiar. We do a GET HTTP request and create a new UserModel object from the response. However, for some reason, we need to account for when the user variable is already instantiated and perform a fill. This is where task two comes in.

In task two, we are performing a few steps: 1. first we subscribe a function (onNotification) to a topic key (“users.{id}”). 2. Once we are successfully subscribed, perform another GET request via loadUser() to account for the situation where notifications were sent during the subscription process. 3. When a notification is received, execute onNotification() to perform the merging of data from the web socket into the current UserModel may have already created.

The important step in task two is step 1 – subscribe to notifications. The moment that this step completes, the page is now allowed to respond to web socket notifications. This is a critical state because it may result in a situation where a notification is received over the web socket before the first GET request from loadUser() (task 1) has completed. In this situation, the notification’s data doesn’t have an issue – onNotification() executes, it sees that the user variable is undefined, so it creates a new instance of UserModel(). However, what should we do once the response from loadUser()’s GET request is received?

Looking at the done() function in loadUser() – the code will see that user already has a value, so it performs a fill(). The reason for using fill here is to ensure that we get eventual consistency in the app because it will not override the notification that was received. We do not perform a merge() function here because our app declares that notifications take precedence over HTTP responses for the same resource. This is because the data from the HTTP request may be stale by the time it is received after the notification. If we performed a merge here, that stale data would have overwritten the data received from the notification and our UI would become out of sync with the backend data. Performing a fill() will instead just add in any missing data attributes to the user value and not overwrite any existing data.

The last step of the subscription logic is step two of when we subscribe to notifications:

Once we are successfully subscribed, perform another GET request via loadUser() to account for the situation where notifications were sent during the subscription process.
This second request is used to handle the situation where the first call to loadUser() has completed and notification data was sent from our backend but before we have finished subscribing onNotification() to respond to the notification. What this means is that the backend data has been updated after the first GET request has completed, but the update message was lost. To account for this situation, we need to perform a second call to loadUser() to ensure that we have the most up-to-date state of the user model from the backend. Fortunately, if any notifications are sent during this second request, onNotification will respond accordingly and those new values will not get overwritten when the second HTTP response is received.

After all is said and done, once the data is loaded and the page has subscribed to user notifications, our user variable will eventually become consistent with the state stored on the backend of our web app.

In summary, when dealing with eventual consistency in your web apps, here are the techniques I have seen success with:

Utilize/implement merge and fill algorithms on your model objects to handle the updating of existing model data in your app.
Treat data received from multiple data sources (web sockets and HTTP requests in this case) as input methods to your page that run in parallel.
Declare that one of these data inputs take precedence over the others. When data is received from the higher priority source, perform a merge to combine with the existing data. All other data input sources should instead perform a fill for new data.

Post external references

  1. 1
    http://blog.johnryding.com/post/89055480988/eventual-consistency-in-real-time-web-apps
Source