Resolving conflicts on the server

Hi all,

we have a typical mobile app configuration where iOS & Android clients sync via the Sync GW.

Instead of resolving conflicts on each platform individually we’re thinking of resolving them on the server (no user input is required).

If that’s possible, where is the best place to detect and resolve conflicts (on the server)?

Are there any docs or example code anywhere to have a look?

Thank you

The documentation here on Resolving Conflicts sounds like exactly what you’re looking for :slight_smile:

Hi thanks for the reply. Makes sense and looks reasonably simple to integrate into the reverse proxy we use. Thanks.

@nkonstas

What is your preferred language for coding for the server side? (I expect to write some blogs on similar topics, so it would be good to know what languages people prefer.)

Thanks,
Hod

simple, always add and update the data using push command and it will maintain the revision for you. During fetching the data, pull the data with latestrevisionid() method and rest it will do for you.
Example -

puller.getLocalDatabase().getDocument(“si”).getCurrentRevision().toString().replace("{si #", “”).replace("}", “”)

@hod.greeley Pretty much all the server code is in C# – About a dozen (native windows) services, (ASP.NET) web apps and the reverse proxy run on IIS. The couchbase DBs & SGs run on ubuntu. All hosted on EC2.

@sidh007 Sorry but I’m not sure I understand the approach – in many cases picking the latest revision is not the desired way to resolve a conflict, e.g for documents that contain collections a merge is desirable.

May I understand for what purpose you really want to revision id?

In couchbase SG, revision is maintain to avoid conflict. For example if there’s any changes in content, it will give new revision and avoid conflict.

Please explain your case in little more detail. Or we can discuss this over email - my email id is taneja.sidh@gmail.com

@sidh007

As far as I understand it, although the SG gives you a ‘current’ revision, that revision may be in conflict. It’s up to the app to resolve it in a meaningful manner either server-side or client-side.

Also, you can have two revisions in conflict where a merge would make more sense (for the app) than simply picking a winning version. e.g. an app like ours where you can add meals to your day. Assume, two devices offline, add 2 meals on one device, 3 meals on the other device. When both devices go online & sync that may lead to a conflict. A reasonable resolution would be to merge the meals and end up with 5 (2 + 3) for that day.

@sidh007 this isn’t really accurate. Revisions and the revision tree aren’t there to avoid conflicts in a linear set of changes. They’re there so you can resolve conflicts between changes made by different sources.

This diagram should help:

Here, the generation 3 revisions represent what happens when two different changes are made to the generation 2 revision.

In this sense, we need to resolve the conflict at client side. As one of the example given below -

private void resolveConflicts(QueryEnumerator rows) {
for (QueryRow row : rows) {
List revs = row.getConflictingRevisions();
if (revs.size() > 1) {
SavedRevision defaultWinning = revs.get(0);
String type = (String) defaultWinning.getProperty(“type”);
switch (type) {
// TRAINING: Automatic conflict resolution
case “task-list”:
case “task-list.user”:
Map<String, Object> props = defaultWinning.getUserProperties();
Attachment image = defaultWinning.getAttachment(“image”);
// resolveConflicts(revs, props, image);
break;
// TRAINING: N-way merge conflict resolution
case “task”:
/
/
./ List mergedPropsAndImage = nWayMergeConflicts(revs);
resolveConflicts(revs, (Map<String, Object>) mergedPropsAndImage.get(0), (Attachment) mergedPropsAndImage.get(1));
//
break;
}
}
}
}

private void resolveConflicts(final List revs, final Map<String, Object> desiredProps, final Attachment desiredImage) {
database.runInTransaction(new TransactionalTask() {
@Override
public boolean run() {
int i = 0;
for (SavedRevision rev : revs) {
UnsavedRevision newRev = rev.createRevision(); // Create new revision
if (i == 0) { // That’s the current/winning revision
newRev.setUserProperties(desiredProps);
if (desiredImage != null) {
try {
newRev.setAttachment(“image”, “image/jpg”, desiredImage.getContent());
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
}
} else { // That’s a conflicting revision, delete it
newRev.setIsDeletion(true);
}

                try {
                    newRev.save(true); // Persist the new revision
                } catch (CouchbaseLiteException e) {
                    e.printStackTrace();
                    return false;
                }
                i++;
            }
            return true;
        }
    });
}

Same example is given couchbase SG git account. In the example, we are resolving image attachment, change it accordingly to resolve conflicts of REV_ID