Couchbase Lite機能解説：競合解決概要


## はじめに

本稿では、Couchbase Liteを用いた開発における競合解決の概要を整理します。

実際の実装における、注意点などは、参考情報に掲載したドキュメントなどを参照してください。


## 競合の原因

アプリケーション間で、データが同期される場合、複数のアプリケーションによって同じドキュメントに変更が加えられた場合、ドキュメントの競合が発生する可能性があります。

競合は、次のいずれかのイベントの後に発生する可能性があります。

- レプリケーションプロセスが、ドキュメントの変更を保存：　この場合、最も大きいリビジョンIDを持つ変更が優先されます。
- アプリケーションが、ドキュメントの変更を保存：　この場合、最後の書き込みが優先されます。

競合発生時、削除操作は常に勝ちます。したがって、上記のいずれの場合でも、変更の1つが削除であった場合、ドキュメントは削除されます。


## レプリケーション時の競合

競合は、レプリケーションプロセスが変更を伝播する場合に発生する可能性があります。 


### 自動競合解決


Couchbase Liteは、次のルールを使用して、レプリケーションの競合を処理します。

- 変更の1つが削除である場合： 削除されたドキュメント（トゥームストーンと呼ばれます）は、常にドキュメントの更新に優先します。
- 上記以外の場合： 最も大きいリビジョンIDを持つ変更が優先されます。

変更のたびに、増分されたバージョン番号が付いたリビジョンIDが作成され、バージョン番号が最も大きい変更が優先されます。

### カスタム競合解決

ドキュメントの競合の処理方法をより細かく制御したいアプリケーション開発者は、カスタムロジックを使用して、競合するリビジョン間で勝者を選択できます。

レプリケーション中にカスタムの競合解決を実装するには、次の手順を実装する必要があります。

- コンフリクトリゾルバーを実装する
- レプリケーターを構成する

#### コンフリクトリゾルバー

アプリケーションが競合を解決するための、次のような戦略があります。

- ローカル優先
- リモート優先
- マージ


##### ローカル勝利

```java
class LocalWinConflictResolver implements ConflictResolver {
    public Document resolve(Conflict conflict) {
        return conflict.getLocalDocument();
    }
}
```

##### リモート勝利

```java
class RemoteWinConflictResolver implements ConflictResolver {
    public Document resolve(Conflict conflict) {
        return conflict.getRemoteDocument();
    }
}
```

##### マージ

```java
class MergeConflictResolver implements ConflictResolver {
    public Document resolve(Conflict conflict) {
        Map<String, Object> merge = conflict.getLocalDocument().toMap();
        merge.putAll(conflict.getRemoteDocument().toMap());
        return new MutableDocument(conflict.getDocumentId(), merge);
    }
}
```

リゾルバーによってnullドキュメントが返されると、競合はドキュメントの削除として解決されます。


#### レプリケーターを構成する

実装されたカスタム競合リゾルバーを、レプリケーター構成オブジェクトに登録します。Resolverのデフォルト値はnullです。nullの場合、デフォルトの競合解決が適用されます。


```java
URLEndpoint target = new URLEndpoint(new URI("ws://localhost:4984/mydatabase"));

ReplicatorConfiguration config = new ReplicatorConfiguration(database, target);
config.setConflictResolver(new LocalWinConflictResolver());

Replicator replication = new Replicator(config);
replication.start();
```

## アプリケーション更新時の競合

アプリケーションがドキュメントを更新するときに、競合の可能性を考慮する必要があります。

更新の競合を引き起こす典型的な一連のイベントは次のとおりです。

1. アプリケーションがドキュメントを読み取ります。
1. レプリケーターがドキュメントを更新します。
1. アプリケーションがドキュメントを（変更し）更新します。

### 自動競合解決

Last-Write-Win（LWW）アルゴリズムを使用して、更新を選択します。


### カスタム競合ハンドラー

開発者は、ドキュメントを保存するときに競合ハンドラーをフックするようにできます。
カスタムの競合解決を実装するには、競合ハンドラーブロック（`database.save（MutableDocument document、ConflictHandlerconflictHandler）`）を使用して`save`メソッドを呼び出します。

次のコードは、既存のドキュメント（`curDoc`）のプロパティを保存中のドキュメント（`newDoc`）にマージする例を示しています。

```java
Document doc = database.getDocument("xyz");
if (doc == null) { return; }
MutableDocument mutableDocument = doc.toMutable();
mutableDocument.setString("name", "apples");

database.save(
    mutableDocument,
    (newDoc, curDoc) -> {
        if (curDoc == null) { return false; }
        Map<String, Object> dataMap = curDoc.toMap();
        dataMap.putAll(newDoc.toMap());
        newDoc.setData(dataMap);
        return true;
    });
```

## 参考情報

https://docs.couchbase.com/couchbase-lite/current/java/conflict.html


https://blog.couchbase.com/document-conflicts-couchbase-mobile/


[Couchbase Mobile データレプリケーションプロトコル解説](https://qiita.com/yoshiyuki_kono/items/57fd83ded0eec7ad8f46)