Optimistic Models 2 — Handling multiple interactions without server confirmation

Taykalo Paul
Stanfy Engineering Practices
6 min readOct 28, 2015

Paul Taykalo, Lead iOS Engineer at Stanfy, a mobile design and development studio based in San Francisco.

Hi there. I hope that you’ve already read the previous post about why you shouldn’t show spinners in your applications, and how you can actually avoid it.

Let’s recap a few things here. Our main goal is to allow the user to feel that every action she performs in the application happens immediately, blazingly fast, even if some actions are performed slowly. We’ll be working with non-confirmed operations here a lot, so we cannot use this approach if we’re about to work with highly-sensitive data, such as credit card information, payment information, etc.

Previously we mentioned that in order to make an application look faster, we can predict some operations and show the results to the user without receiving actual confirmation from the server. Despite the fact that there is always a delay when an application communicates with the server, we were able to successfully hide that from the users.

However, this will work well only for simple situations, when the user doesn’t perform actions too quickly, and when we’re actually able to perform them fast enough and not bother about cases, where multiple concurrent operations are performed on the same object. In case there are multiple operations performed, we’ll need something a bit more complex than a simple optimistic model.

Users can be pretty damn fast

Let’s consider the following example. The user is working with an object (let’s say a post) and every operation she performs needs to be confirmed by the server.

So here is a list of actions that the user performs:

  • likes the post
  • adds a comment to the post
  • changes post rating from 2 to 5
  • unlikes the post
  • updates the title to “Handling multiple interactions without server confirmation”
  • changes the post rating from 5 to 2
  • changes post rating from 2 to 4
  • likes the post

Let’s say that there’s no such thing as a “Save button” on the screen, and the user expects that every change she makes will be automatically saved at some point. Also, let’s imagine that we cannot wait until the user finishes all his operations (In real life we could probably wait some time before sending a request to the server, then group changes that the user has made, and only then send a request).

One screen — multiple user actions

Base implementation of this logic can be described like so: Just send the update once the user changes something, and show the results from the server. This implementation often leads to blinking and showing incorrect (from the user perspective, but 100% confirmed on the server side) information to the user.

Expectations vs Actual result

It probably still can work fine… until operations start to fail.

If we aren’t using an optimistic model, it will be hell; the only thing you can do then is to tell the user “Sorry, something went wrong; please revise all the changes you’ve made in the last four minutes, and check to see if everything looks fine.” Some application decides to show tons of alerts — for each failed operation.

Error alert’s hell

But even using the optimistic model, it’s hard to correctly handle this situation. If there are a lot of operations, we would have a state based on unapproved states and this chain will break. :(

If we miss one operation in chain…Things will go wrong..

Storing confirmed object and updates

In order to handle those situations, we could use another trick — first, we need some Model in our application which can calculate the state of the object by giving the model a Starting state and list of operations which should be performed on that object.

Predicting value based on list of operations being performed

Next, when an operation is performed on the object, we add this operation to the list of unconfirmed operations, and recalculate the current object state. Once the operation succeeds, we should update the confirmed object and remove the finished operation from the list, and then recalculate the state again. If the operation fails, we just need to remove this operation from the list and recalculate the visible state.

That’s all. Just by adding this one simple rule, we can correctly rollback our failed operations.

Queueing updates

In order to simplify your life, I highly recommend preventing concurrent updates on the object. On one hand it will require having some queue for each object, but on the other hand it will allow you to handle complex cases faster.

Grouping and discarding operations

When we have list of operations that need to be performed on the object, we can simply run them on the server one by one, and update the visual state correspondingly, using the confirmed object + set of operations which are left.

If we know that a list of operations can become too big, we could start to use some optimisations in order to decrease it.

Take a look at the previous example, with the post. What can we change there?

  • If our server accepts object updates and we can send the whole object with changes, we would next: {like}, {comment:, rating:4, title:”title”}.
All these operations can be grouped to one server call
  • If our server doesn’t accept object updates and works per property basis, we still have some things to optimise.

Retrying operations

We can add one more thing — if the operation fails, we won’t throw it away immediately. Instead, we would retry it again a few times after some delays (Actually, it’s good to have this optimisation even if we’re not using optimistic models). :)

We can go even further. Instead of blindly repeating the same operation, upon operation failure, we can check whether or not the result of this operation was already overridden by the next operations. If so, we can throw away the current operation and start the next one.

We can throw away some operations, since they’re not actual anymore

Sum up

We’re often bound to the servers in our applications. The server is the king. The server decides what to do, what the current state is, and how to perform complex business logic. Often the connection to that server isn’t fast and not reliable enough. By moving some parts of simple logic on the client side, we shouldn’t often have to wait for confirmations and can go on even without server confirmation.

P.S

This article was intended to be published long time ago, and since then a lot of things happened. One of them is that Parse has revealed their SDK source, and there you can find, that some operations are performed in the way, similar to described in this article. Still, I decided to publish this article, may be not everyone have looked to the Parse sources yet :))

P.P.S

This is technically repost of the post that was written on Stanfy’s blog. But since there are a lot of people who are following me on Medium, I will repost all my posts here too. :) (I don’t like the idea how Medium works with comments, so better ask me questions in the blog)

If you liked the article, hit that heart button below. Would mean a lot to me. :)

--

--

Taykalo Paul
Stanfy Engineering Practices

I’m a Software Engineer at MacPaw. I like and I do programming