You describe a classic race condition where 2 users create a different operation over the same resource (in our use-case - a record in db)
Short answer: record versioning - manage a unique version number for each record. changes can only be executed when deliver with the same version value
Long answer: Suppose we are using a no-sql db where every list is saved in lists collection where every record looks something like this:
{
id: 'uuid-4',
items: [
{ name: 'item #1', checked: false },
{ name: 'item #2', checked: true },
{ name: 'item #3', checked: false }
],
version: 1648727895753
}
In the example above, I've use a timestamp as a record version (using the new Date().getTime() method). Every time this record is updated - the version changes as well.
Now - let's describe your scenario again:
2 users downloading the same todo list from server. At this point in time - both users have the same version value (let's say, version A). Now, let's assume that one user made an update while the other user is offline (in your example - due to traveling). In order for this pattern to work, we change the version value after each successful update so now the record has a new version value (let's say, version B). When the other user will try to make a change - it will be blocked because he still got the old version number (in our example A). In order to make a change - the newest version (B) should be downloaded first, then the user will update his own edits and write his changes to db (this process will result with an updated record with version C)
So in order to make this work, one should create the following mechanism:
Server Side (db logic)
- on record update
- make update only if request version value equals to record version value
- on fetch record
- return record with current version value
Client side (app logic)
- on app load
- fetch current todo list record (by
id field)
- on record update
- send update to server with current version value
- if update fails:
- save current todo list record as
old version
- fetch current todo list record (by
id field)
- merge changes (you will decide how to reflect it to user)
- send update to server with current version value (it's now the most updated one)
Technical Notes
- Merging between 2 record versions (e.g. - between to JSON-based object) is a whole universe by its own. You may use a 3rd-party library to spot the differences (such as json-diff) or decide to apply a default behavior (for instance: if an item from list was deleted in db it will have a different style apply so the user understand that this item was removed)
- Even now we still risk to have a race condition (well.. highly unlikely when using a simple
todo list but may occur in application with high loads). For production ready setup - please consider using a message queue in order to apply all actions by a specific order (first in first out). Again - it's an overkill to your use-case but since we are solving here a more general problem - I think it's worth mentioning