{"id":33181059,"url":"https://github.com/netopyr/wurmloch-crdt","last_synced_at":"2026-01-14T03:14:25.357Z","repository":{"id":54600784,"uuid":"80272641","full_name":"netopyr/wurmloch-crdt","owner":"netopyr","description":"Experimental CRDT-implementations for the JVM","archived":false,"fork":false,"pushed_at":"2017-03-15T16:10:55.000Z","size":147,"stargazers_count":125,"open_issues_count":2,"forks_count":23,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-07-16T03:43:47.635Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/netopyr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-28T07:39:15.000Z","updated_at":"2025-06-12T02:59:59.000Z","dependencies_parsed_at":"2022-08-13T21:00:40.882Z","dependency_job_id":null,"html_url":"https://github.com/netopyr/wurmloch-crdt","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/netopyr/wurmloch-crdt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netopyr%2Fwurmloch-crdt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netopyr%2Fwurmloch-crdt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netopyr%2Fwurmloch-crdt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netopyr%2Fwurmloch-crdt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/netopyr","download_url":"https://codeload.github.com/netopyr/wurmloch-crdt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netopyr%2Fwurmloch-crdt/sbom","scorecard":{"id":680921,"data":{"date":"2025-08-11","repo":{"name":"github.com/netopyr/wurmloch-crdt","commit":"72e6edea14ec3d328dbffad46d0f92fa71dc5b4d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-j288-q9x7-2f5v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T23:08:31.753Z","repository_id":54600784,"created_at":"2025-08-21T23:08:31.753Z","updated_at":"2025-08-21T23:08:31.753Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408818,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-11-16T04:00:23.843Z","updated_at":"2026-01-14T03:14:25.345Z","avatar_url":"https://github.com/netopyr.png","language":"Java","funding_links":[],"categories":["分布式开发","Implementations"],"sub_categories":["Spring Cloud框架","Data Structures"],"readme":"# wurmloch-crdt\nExperimental implementations of conflict-free replicated data types (CRDTs) for the JVM\n\n[![Bintray](https://img.shields.io/bintray/v/netopyr/wurmloch/wurmloch-crdt.svg?colorB=0081c4)](https://bintray.com/netopyr/wurmloch/wurmloch-crdt)\n[![Maven](https://img.shields.io/maven-central/v/com.netopyr.wurmloch/wurmloch-crdt.svg)](https://search.maven.org/#search|ga|1|com.netopyr.wurmloch)\n\n\n## What is a CRDT?\n\nA conflict-free replicated data type (abbreviated CRDT) can be used in distributed computing to share data between nodes.\nIt is a data structure with specifc features.\nEach node contains its private copy of a CRDT (called replica), which it modifies locally without the need to synchronize immediately.\nHowever, when two replicas are connected, they can synchronize automatically in the background.\nTo achieve that, CRDTs are designed in such a way that there can never be any conflicts between concurrent updates.\n\n## Adding wurmloch-crdt to your project\n\n### Maven\n```xml\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.netopyr.wurmloch\u003c/groupId\u003e\n    \u003cartifactId\u003ewurmloch-crdt\u003c/artifactId\u003e\n    \u003cversion\u003e0.1.0\u003c/version\u003e\n  \u003c/dependency\u003e\n```\n\n### Gradle\n```groovy\n  dependencies {\n      compile 'com.netopyr.wurmloch:wurmloch-crdt:0.1.0'\n  }\n```\n\n## Examples\n\nThe following section explains the CRDTs available in wurmloch-crdt and shows examples of their usage.\n\n### CrdtStore-Example\n\nIn wurmloch-crdt each node of a distributed application contains a CrdtStore that manages the CRDTs.\nIt offers functionality to add CRDTs and find CRDTs that were added by other nodes.\n\nTo be able to identify CRDTs, they have to be created with a unique ID.\nIf no ID is provided, a random UUID is used.\n\nIn real-world scenarios, the CrdtStores would run in different nodes on different JVMs and if they are connected depends solely on the status of the network.\nBut wurmloch-crdt also contains a local implementation LocalCrdtStore, which connection state can be controlled by calling methods connect() and disconnect().\n\nThe following examples creates two LocalCrdtStores in which our first CRDT, a G-Set, is created.\n\n```java\n    // create two CrdtStores\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n\n    // create a new G-Set\n    crdtStore1.createGSet(\"ID_1\");\n\n    // at this point the CrdtStores are not connected, therefore the new G-Set is unknown in the second store\n    assertThat(crdtStore2.findCrdt(\"ID_1\").isDefined(), is(false));\n\n    // connect both stores\n    crdtStore1.connect(crdtStore2);\n\n    // now the new G-Set is also known in the second store\n    assertThat(crdtStore2.findCrdt(\"ID_1\").isDefined(), is(true));\n```\n_Code Sample 1: Using the LocalCrdtStore (see [CrdtStoreExample][class crdtstoreexample])_\n\n### G-Set \n\nA G-Set or Grow-Only Set is a Set to which elements can only be added and never removed.\nIt is probably the simplest CRDT.\nSynchronizing two G-Set that have diverged is accomplished by calculating the union of both Sets.\n\nOn first sight, a Set where elements can never be removed might seem superfluous.\nBut there are actually a lot of use cases, where such a limited Set is useful.\nFor example most domain entities in business applications are never really removed for auditing reasons and therefore could be stored in a G-Set.\nAlso it is a common practice to build complex CRDTs upon simple CRDTs.\nAll of the more complex CRDT-Sets, that also allow removes, are built on top of G-Sets.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create a G-Set and find the according replica in the second store\n    final GSet\u003cString\u003e replica1 = crdtStore1.createGSet(\"ID_1\");\n    final GSet\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindGSet(\"ID_1\").get();\n\n    // add one entry to each replica\n    replica1.add(\"apple\");\n    replica2.add(\"banana\");\n\n    // the stores are connected, thus the G-Set is automatically synchronized\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"banana\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"banana\"));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // add one entry to each replica\n    replica1.add(\"strawberry\");\n    replica2.add(\"pear\");\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"banana\", \"strawberry\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"banana\", \"pear\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // the G-Set is synchronized automatically and contains now all elements\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"banana\", \"strawberry\", \"pear\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"banana\", \"strawberry\", \"pear\"));\n```\n_Code Sample 2: Using a G-Set (see [GSetExample][class gsetexample])_\n\n### G-Counter\n\nA G-Counter or increment-only Counter is - as the name suggests - an integer counter, that one can only incremented.\nIt has methods to increment and request the current value.\nWhen synchronized, the value converges towards the sum of all increments.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create a G-Counter and find the according replica in the second store\n    final GCounter replica1 = crdtStore1.createGCounter(\"ID_1\");\n    final GCounter replica2 = crdtStore2.findGCounter(\"ID_1\").get();\n\n    // increment both replicas of the counter\n    replica1.increment();\n    replica2.increment(2L);\n\n    // the stores are connected, thus the replicas are automatically synchronized\n    assertThat(replica1.get(), is(3L));\n    assertThat(replica2.get(), is(3L));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // increment both counters again\n    replica1.increment(3L);\n    replica2.increment(5L);\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1.get(), is(6L));\n    assertThat(replica2.get(), is(8L));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // the counter is synchronized automatically and contains now the sum of all increments\n    assertThat(replica1.get(), is(11L));\n    assertThat(replica2.get(), is(11L));\n```\n_Code Sample 3: Using a G-Counter (see [GCounterExample][class gcounterexample])_\n\n### PN-Counter\n\nA PN-Counter is an integer-counter, that can be incremented and decremented.\nWhen synchronized, the value converges towards the sum of all increments minus the sum of all decrements.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create a PN-Counter and find the according replica in the second store\n    final PNCounter replica1 = crdtStore1.createPNCounter(\"ID_1\");\n    final PNCounter replica2 = crdtStore2.findPNCounter(\"ID_1\").get();\n\n    // change the value of both replicas of the counter\n    replica1.increment();\n    replica2.decrement(2L);\n\n    // the stores are connected, thus the replicas are automatically synchronized\n    assertThat(replica1.get(), is(-1L));\n    assertThat(replica2.get(), is(-1L));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // update both counters again\n    replica1.decrement(3L);\n    replica2.increment(5L);\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1.get(), is(-4L));\n    assertThat(replica2.get(), is(4L));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // the counter is synchronized automatically and contains now the sum of all increments minus all decrements\n    assertThat(replica1.get(), is(1L));\n    assertThat(replica2.get(), is(1L));\n```\n_Code Sample 4: Using a PN-Counter (see [PNCounterExample][class pncounterexample])_\n\n### LWW-Register\n\nA Register stores a single Object.\nIt contains a get()- and a set()-method to read and write the value.\nIn an LWW-Register (Last-Writer-Wins Register), the last set-call will supersede previous calls.\nInternally a LWWRegister uses a [VectorClock][wiki vectorclock] to keep track of the time.\nIf two updates happen concurrently in disconnected replicas, the one from the store with the smaller Id will take precedence.\n\nPlease note that a last-writer-wins strategy results in data loss, if data is modified concurrently.\nThis is ok in some use-cases, but has to be avoided in others.\nMV-Registers (see below) provide a much more sophisticated logic for these kind of cases.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore(\"N_1\");\n    final CrdtStore crdtStore2 = new CrdtStore(\"N_2\");\n    crdtStore1.connect(crdtStore2);\n\n    // create an LWW-Register and find the according replica in the second store\n    final LWWRegister\u003cString\u003e replica1 = crdtStore1.createLWWRegister(\"ID_1\");\n    final LWWRegister\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindLWWRegister(\"ID_1\").get();\n\n    // set values in both replicas\n    replica1.set(\"apple\");\n    replica2.set(\"banana\");\n\n    // the stores are connected, thus the last write wins\n    assertThat(replica1.get(), is(\"banana\"));\n    assertThat(replica2.get(), is(\"banana\"));\n\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // add one entry to each replica\n    replica1.set(\"strawberry\");\n    replica2.set(\"pear\");\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1.get(), is(\"strawberry\"));\n    assertThat(replica2.get(), is(\"pear\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // the LWW-Register is synchronized automatically.\n    // as the update happened concurrently, the update from the node with the larger ID wins\n    assertThat(replica1.get(), is(\"pear\"));\n    assertThat(replica2.get(), is(\"pear\"));\n```\n_Code Sample 5: Using an LWW-Register (see [LWWRegisterExample][class lwwregisterexample])_\n\n### MV-Register\n\nAn MV-Register (Multi-Value Register) is another implementation of a register.\nIt avoids the kind of data loss, that is inherent to any kind of last-writer-wins strategy.\nInstead if the value of a MV-Register is changed concurrently, it keeps all values.\nTherefore the result of the get()-method is a collection.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create an MV-Register and find the according replica in the second store\n    final MVRegister\u003cString\u003e replica1 = crdtStore1.createMVRegister(\"ID_1\");\n    final MVRegister\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindMVRegister(\"ID_1\").get();\n\n    // set values in both replicas\n    replica1.set(\"apple\");\n    replica2.set(\"banana\");\n\n    // the stores are connected, thus we can determine the order of both writes\n    // the latter write overrides the previous one\n    assertThat(replica1.get(), contains(\"banana\"));\n    assertThat(replica2.get(), contains(\"banana\"));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // change the value in both replicas\n    replica1.set(\"strawberry\");\n    replica2.set(\"pear\");\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1.get(), contains(\"strawberry\"));\n    assertThat(replica2.get(), contains(\"pear\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // as the update happened concurrently, we cannot determine an order and both values are kept\n    assertThat(replica1.get(), containsInAnyOrder(\"strawberry\", \"pear\"));\n    assertThat(replica2.get(), containsInAnyOrder(\"strawberry\", \"pear\"));\n\n    // update the value one more time\n    replica2.set(\"orange\");\n\n    // the last update was clearly after the concurrent ones, therefore both replicas contain the last value only\n    assertThat(replica1.get(), contains(\"orange\"));\n    assertThat(replica2.get(), contains(\"orange\"));\n```\n_Code Sample 6: Using an MV-Register (see [MVRegisterExample][class mvregisterexample])_\n\nNote that a MV-Register is not a Set.\nAs can be seen in the following more complex example, an MV-Register keeps track of which values were overriden and can be eliminated.\n\n```java\n    // create three CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    final CrdtStore crdtStore3 = new CrdtStore();\n    crdtStore2.connect(crdtStore1);\n    crdtStore2.connect(crdtStore3);\n\n    // create an MV-Register and find the according replica in the other stores\n    final MVRegister\u003cString\u003e replica1 = crdtStore1.createMVRegister(\"ID_1\");\n    final MVRegister\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindMVRegister(\"ID_1\").get();\n    final MVRegister\u003cString\u003e replica3 = crdtStore3.\u003cString\u003efindMVRegister(\"ID_1\").get();\n\n    // disconnect store 2 and 3\n    crdtStore2.disconnect(crdtStore3);\n\n    // set some values\n    replica1.set(\"apple\");\n    replica3.set(\"banana\");\n\n    // store 1 and 2 contain \"apple\", store 3 contains \"banana\"\n    assertThat(replica1.get(), containsInAnyOrder(\"apple\"));\n    assertThat(replica2.get(), containsInAnyOrder(\"apple\"));\n    assertThat(replica3.get(), containsInAnyOrder(\"banana\"));\n\n    // disconnect store 1 and 2 and connect store 2 and 3 instead\n    crdtStore2.disconnect(crdtStore1);\n    crdtStore2.connect(crdtStore3);\n\n    // set the register in store 1 to \"strawberry\"\n    replica1.set(\"strawberry\");\n\n    // store 1 still contains \"strawberry\" only\n    // store 2 and 3 are synchronized and contain \"apple\" and \"banana\", because these updates happened concurrently\n    assertThat(replica1.get(), containsInAnyOrder(\"strawberry\"));\n    assertThat(replica2.get(), containsInAnyOrder(\"apple\", \"banana\"));\n    assertThat(replica3.get(), containsInAnyOrder(\"apple\", \"banana\"));\n\n    // connect all stores again\n    crdtStore2.connect(crdtStore1);\n\n    // the result is not simply the union of all values\n    // \"apple\" was overridden by \"strawberry\", therefore it disappears now\n    // \"banana\" and \"strawberry\" were set concurrently, thus both values are still in the result\n    assertThat(replica1.get(), containsInAnyOrder(\"banana\", \"strawberry\"));\n    assertThat(replica2.get(), containsInAnyOrder(\"banana\", \"strawberry\"));\n    assertThat(replica3.get(), containsInAnyOrder(\"banana\", \"strawberry\"));\n```\n_Code Sample 7: A more complex example using an MV-Register (see [MVRegisterComplexExample][class mvregistercomplexexample])_\n\n### OR-Set\n\nThe OR-Set (Observed-Remove Set) is a CRDT which probably comes closes to the expected behavior of a Set.\nThe basic idea is, that only elements which add-operation is visible to a replica can be removed from that replica.\nThat means for example, if an element is added in one replica and at the same time removed from a second, non-synchronized replica, it is still contained in the OR-Set, because the add-operation was not visible to the second replica yet.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create a G-Set and find the according replica in the second store\n    final ORSet\u003cString\u003e replica1 = crdtStore1.createORSet(\"ID_1\");\n    final ORSet\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindORSet(\"ID_1\").get();\n\n    // add one entry to each replica\n    replica1.add(\"apple\");\n    replica2.add(\"banana\");\n\n    // the stores are connected, thus the G-Set is automatically synchronized\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"banana\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"banana\"));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // remove one of the entries\n    replica1.remove(\"banana\");\n    replica2.add(\"strawberry\");\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1, containsInAnyOrder(\"apple\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"banana\", \"strawberry\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // \"banana\" was added before both stores got disconnected, therefore it is now removed during synchronization\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"strawberry\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"strawberry\"));\n\n    // disconnect the stores again\n    crdtStore1.disconnect(crdtStore2);\n\n    // add one entry to each replica\n    replica1.add(\"pear\");\n    replica2.add(\"pear\");\n    replica2.remove(\"pear\");\n\n    // \"pear\" was added in both stores concurrently, but immediately removed from replica2\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"strawberry\", \"pear\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"strawberry\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // \"pear\" was added in both replicas concurrently\n    // this means that the add-operation of \"pear\" to replica1 was not visible to replica2\n    // therefore removing \"pear\" from replica2 does not include removing \"pear\" from replica1\n    // as a result \"pear\" reappears in the merged Sets\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"strawberry\", \"pear\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"strawberry\", \"pear\"));\n```\n_Code Sample 8: Using an OR-Set (see [ORSetExample][class orsetexample])_\n\n### RGA\n\nAn RGA (Replicated Growable Array) is a CRDT that behaves similar to a List.\nThe elements have an order and one can add and remove elements at specific positions.\nIn this implementation, it is not possible though to set values, because it is not defined how concurrent sets should behave.\nAn UnsupportedOperationException will be thrown if the set() method is called.\n\n```java\n    // create two CrdtStores and connect them\n    final CrdtStore crdtStore1 = new CrdtStore();\n    final CrdtStore crdtStore2 = new CrdtStore();\n    crdtStore1.connect(crdtStore2);\n\n    // create a G-Set and find the according replica in the second store\n    final RGA\u003cString\u003e replica1 = crdtStore1.createRGA(\"ID_1\");\n    final RGA\u003cString\u003e replica2 = crdtStore2.\u003cString\u003efindRGA(\"ID_1\").get();\n\n    // add one entry to each replica\n    replica1.add(\"apple\");\n    replica2.add(\"banana\");\n\n    // the stores are connected, thus the RGA is automatically synchronized\n    assertThat(replica1, contains(\"apple\", \"banana\"));\n    assertThat(replica2, contains(\"apple\", \"banana\"));\n\n    // disconnect the stores simulating a network issue, offline mode etc.\n    crdtStore1.disconnect(crdtStore2);\n\n    // add one entry to each replica\n    replica1.remove(\"banana\");\n    replica2.add(1, \"strawberry\");\n\n    // the stores are not connected, thus the changes have only local effects\n    assertThat(replica1, containsInAnyOrder(\"apple\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"strawberry\", \"banana\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // the RGA is synchronized automatically\n    assertThat(replica1, containsInAnyOrder(\"apple\", \"strawberry\"));\n    assertThat(replica2, containsInAnyOrder(\"apple\", \"strawberry\"));\n\n    // disconnect the stores\n    crdtStore1.disconnect(crdtStore2);\n\n    // set() is not supported in an RGA\n    // if we try to simulate with a remove and add, we can see the problem\n    replica1.remove(0);\n    replica1.add(\"pear\");\n    replica2.remove(0);\n    replica2.add(\"orange\");\n\n    // the first entry has been replaced\n    assertThat(replica1, containsInAnyOrder(\"pear\", \"strawberry\"));\n    assertThat(replica2, containsInAnyOrder(\"orange\", \"strawberry\"));\n\n    // reconnect the stores\n    crdtStore1.connect(crdtStore2);\n\n    // we have actually added two elements, the RGA keeps both\n    assertThat(replica1, containsInAnyOrder(\"orange\", \"pear\", \"strawberry\"));\n    assertThat(replica2, containsInAnyOrder(\"orange\", \"pear\", \"strawberry\"));\n```\n_Code Sample 9: Using an RGA (see [RGAExample][class rgaexample])_\n\n## Further Readings\n\n* Wikipedia: [Conflict-free replicated data type][wikipedia crdt]\n* [A comprehensive study of Convergent and Commutative Replicated Data Types][crdt article]\n\n[wikipedia crdt]: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\n[crdt article]: http://hal.upmc.fr/file/index/docid/555588/filename/techreport.pdf\n[wiki vectorclock]: https://en.wikipedia.org/wiki/Vector_clocks\n\n[class crdtstoreexample]: src/test/java/com/netopyr/wurmloch/integration/examples/CrdtStoreExample.java\n[class gsetexample]: src/test/java/com/netopyr/wurmloch/integration/examples/GSetExample.java\n[class gcounterexample]: src/test/java/com/netopyr/wurmloch/integration/examples/GCounterExample.java\n[class pncounterexample]: src/test/java/com/netopyr/wurmloch/integration/examples/PNCounterExample.java\n[class lwwregisterexample]: src/test/java/com/netopyr/wurmloch/integration/examples/LWWRegisterExample.java\n[class mvregisterexample]: src/test/java/com/netopyr/wurmloch/integration/examples/MVRegisterExample.java\n[class mvregistercomplexexample]: src/test/java/com/netopyr/wurmloch/integration/examples/MVRegisterComplexExample.java\n[class orsetexample]: src/test/java/com/netopyr/wurmloch/integration/examples/ORSetExample.java\n[class rgaexample]: src/test/java/com/netopyr/wurmloch/integration/examples/RGAExample.java\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnetopyr%2Fwurmloch-crdt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnetopyr%2Fwurmloch-crdt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnetopyr%2Fwurmloch-crdt/lists"}