{"id":23577922,"url":"https://github.com/couchbaselabs/couchversion","last_synced_at":"2025-05-05T23:26:58.448Z","repository":{"id":43255615,"uuid":"298297670","full_name":"couchbaselabs/CouchVersion","owner":"couchbaselabs","description":null,"archived":false,"fork":false,"pushed_at":"2022-10-26T19:11:47.000Z","size":88,"stargazers_count":12,"open_issues_count":2,"forks_count":6,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-31T00:41:20.198Z","etag":null,"topics":["couchbase","database","hacktoberfest","java"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/couchbaselabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-24T14:05:23.000Z","updated_at":"2023-05-08T11:18:03.000Z","dependencies_parsed_at":"2022-08-12T10:21:32.556Z","dependency_job_id":null,"html_url":"https://github.com/couchbaselabs/CouchVersion","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbaselabs%2FCouchVersion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbaselabs%2FCouchVersion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbaselabs%2FCouchVersion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbaselabs%2FCouchVersion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/couchbaselabs","download_url":"https://codeload.github.com/couchbaselabs/CouchVersion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252592098,"owners_count":21773204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["couchbase","database","hacktoberfest","java"],"created_at":"2024-12-26T22:31:48.446Z","updated_at":"2025-05-05T23:26:58.423Z","avatar_url":"https://github.com/couchbaselabs.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n\n**CouchVersion** is a Java framework which helps you to *manage changes* in your Couchbase and *synchronize* them with your application.\nThe concept is very similar to other db migration tools such as Liquibase or [Flyway](http://flywaydb.org) but *without using XML/JSON/YML files*.\n\n# Quick Update:\n\nWe are testing the support for collections. Expectation is to get a new release by Nov 15th 2022.\n\nCouchVersion provides new approach for adding changes (change sets) based on Java classes and methods with appropriate annotations.\n\nThe goal is to keep this tool simple and comfortable to use.\n\nWatch more about it here: https://share.vidyard.com/watch/98GgKFiyi4tz2WuLQVVfzW\n\n\n# Getting started\n\nYou can clone the sample project here https://github.com/deniswsrosa/CouchVersionDemo\n\n# Release Notes\n\n0.5.1 -\u003e Minor bug fixes\n\n## Add a dependency\n\n*IMPORTANT:* https://oss.sonatype.org/content/groups/public/com/github/couchbaselabs/couchversion/\n\nWith Maven\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.couchbaselabs\u003c/groupId\u003e\n  \u003cartifactId\u003ecouchversion\u003c/artifactId\u003e\n  \u003cversion\u003e0.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\nWith Gradle\n```groovy\ncompile 'org.javassist:javassist:3.18.2-GA' // workaround for ${javassist.version} placeholder issue*\ncompile 'com.github.couchversion:couchversion:0.5'\n```\n\n## Usage with Spring\n\nYou need to instantiate CouchVersion object and provide some configuration.\nIf you use Spring can be instantiated as a singleton bean in the Spring context. \nIn this case the migration process will be executed automatically on startup.\n\n```java\n\n@Autowired\nprivate ApplicationContext context;\n\n@Bean\npublic CouchVersion couchversion(){\n  CouchVersion runner = new CouchVersion(context); //It will grab all the data needed from the application.properties file\n  runner.setChangeLogsScanPackage(\n       \"com.example.yourapp.changelogs\"); // the package to be scanned for changesets\n  \n  return runner;\n}\n```\n\nFor the case above, the following properties will be loaded from your **application.properties** file:\n\n```properties\nspring.couchbase.connection=\nspring.couchbase.user=\nspring.couchbase.password=\nspring.couchbase.bucket=\n```\n\nWith **Spring Data Couchbase 4** you can also reuse your class that extends *AbstractCouchbaseConfiguration* (used to connect with Couchbase) to configure CouchVersion:\n\n```java\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;\n\n@Configuration\npublic class CouchbaseConfig extends AbstractCouchbaseConfiguration {\n\n\n    @Override\n    public String getConnectionString() {\n        return \"couchbase://127.0.0.1\";\n    }\n\n    @Override\n    public String getUserName() {\n        return \"Administrator\";\n    }\n\n    @Override\n    public String getPassword() {\n        return \"password\";\n    }\n\n    @Override\n    public String getBucketName() {\n        return \"default\";\n    }\n\n}\n\n```\nand then:\n\n```java\n\n    @Autowired\n    private CouchbaseConfig couchbaseConfig;\n\n    @Autowired\n\tprivate ApplicationContext context;\n    \n\t@Bean\n\tpublic CouchVersion couchVersions(){\n\t\tCouchVersion runner = new CouchVersion(couchbaseConfig.getConnectionString(),\n\t\t\t\tcouchbaseConfig.getBucketName(), couchbaseConfig.getUserName(), couchbaseConfig.getPassword());\n\t\t//if you don't set the application context, your migrations can't be autowired\n\t\trunner.setApplicationContext(context);\n\t\trunner.setChangeLogsScanPackage(\n\t\t\t\t\"com.cb.springdata.sample.migration\"); // the package to be scanned for changesets\n\t\treturn runner;\n\t}\n\n\n```\n\n\n## Usage without Spring\nUsing CouchVersion without a spring context has similar configuration but you have to remember to run `execute()` method to start a migration process.\n\n```java\nCouchVersion runner = new CouchVersion(\"couchbase://SOME_IP_ADDRESS\", \"yourBucketName\", \"bucketPasword\");\nrunner.setChangeLogsScanPackage(\n     \"com.example.yourapp.changelogs\"); // package to scan for changesets\n\nrunner.execute();         //  ------\u003e starts migration changesets\n```\n\nAbove examples provide minimal configuration. `CouchVersion` object provides some other possibilities (setters) to make the tool more flexible:\n\n```java\nrunner.setEnabled(shouldBeEnabled);              // default is true, migration won't start if set to false\n```\n\n\n## Creating change logs\n\n`ChangeLog` contains bunch of `ChangeSet`s. `ChangeSet` is a single task (set of instructions made on a database). In other words `ChangeLog` is a class annotated with `@ChangeLog` and containing methods annotated with `@ChangeSet`.\n\n```java \npackage com.example.yourapp.changelogs;\n\n@ChangeLog\npublic class DatabaseChangelog {\n  \n  @ChangeSet(order = \"1\", id = \"someChangeId\", author = \"testAuthor\")\n  public void importantWorkToDo(){\n     // task implementation\n  }\n\n}\n```\n### @ChangeLog\n\nClass with change sets must be annotated by `@ChangeLog`. There can be more than one change log class but in that case `order` argument should be provided:\n\n```java\n@ChangeLog(order = \"001\")\npublic class DatabaseChangelog {\n  //...\n}\n```\nChangeLogs are sorted *alphabetically* (that is why it is a good practice to start the order with zeros) by `order` argument and changesets are applied due to this order.\n\n### @ChangeSet\n\nMethod annotated by @ChangeSet is taken and applied to the database. History of applied change sets is stored in a document with type `dbChangeLog`:\n\n![CouchVersion](https://raw.githubusercontent.com/deniswsrosa/liquicouch/master/misc/dbChangeLogExample.png)\n\n#### Annotation parameters:\n\n`order` - string for sorting change sets in one changelog. Sorting in alphabetical order, ascending. It can be a number, a date etc.\n\n`id` - name of a change set, **must be unique** for all change logs in a database\n\n`author` - author of a change set\n\n`runAlways` - _[optional, default: false]_ changeset will always be executed but only the first execution event will be stored as a document\n\n`restartInterrupted` - _[optional, default: true]_ changeset will be executed after an interruption, such as an application shutdown, or server a crash.\n\n`retries` - _[optional, default: 0]  If by some reason your changeSet throws an exception and you want to retry it instead of failing, you could set here the number of retries you want (Not sure if this feature is useful, let me know if you are using it). If all retries fail, an exception will be thrown an the application will fail to start.\n\n![CouchVersion](https://raw.githubusercontent.com/deniswsrosa/liquicouch/master/misc/retriesExample.png)\n\n\n#### Defining ChangeSet methods\nMethod annotated by `@ChangeSet` can have one of the following definition:\n\n\n```java\n\n/**\n * If you are using Spring, you can Autowire your Services or Repositories\n */\n@Component\n@ChangeLog(order = \"001\")\npublic class Migration1 {\n\n    @Autowired // Yes, You can a\n    private UserService userService;\n\n    @Autowired\n    private UserRepository userRepository;\n\n    @ChangeSet(order = \"001\", id = \"someChangeId1\", author = \"testAuthor\")\n    public void importantWorkToDo(Cluster cluster, Bucket bucket){\n        System.out.println(\"----------Migration1 - Method1\");\n    }\n\n    @ChangeSet(order = \"002\", id = \"someChangeId2\", author = \"testAuthor\")\n    public void method2(Bucket bucket, Cluster cluster){\n        System.out.println(\"----------Migration1 - Method2\");\n    }\n\n    @ChangeSet(order = \"003\", id = \"someChangeId3\", author = \"testAuthor\")\n    public void method3(Cluster cluster){\n        System.out.println(\"----------Migration1 - Method3\");\n    }\n\n    @ChangeSet(order = \"004\", id = \"someChangeId4\", author = \"testAuthor\")\n    public void method4(Bucket bucket){\n        System.out.println(\"----------Migration1 - Method4\");\n    }\n\n    @ChangeSet(order = \"005\", id = \"someChangeId4\", author = \"testAuthor\")\n    public void method5(){\n        System.out.println(\"----------Migration1 - Method5 (The bucket parameter is not necessary here)\");\n    }\n\n\n}\n\n\n```\n\n\n##### Example\n\nHere is an example of how a real migration could look like:\n\n```java\n/**\n * This is an example of how to use it without Spring, in this case you can execute all the queries via the Bucket argument.\n */\n@ChangeLog(order = \"2\")\npublic class Migration2 {\n\n    @ChangeSet(order = \"001\", id = \"createDummyData\", author = \"testAuthor\")\n    public void createDummyData(Bucket bucket){\n\n        User user1 = new User(UUID.randomUUID().toString(), \"Denis\", null, null, null, null);\n        userRepository.save(user1);\n\n        User user2 = new User(UUID.randomUUID().toString(), \"John\", null, null, null, null);\n        userRepository.save(user2);\n    }\n\n\n    @ChangeSet(order = \"002\", id = \"createPrimaryIndex\", author = \"testAuthor\")\n    public void createInitialIndes(Cluster cluster, Bucket bucket){\n        cluster.queryIndexes().createPrimaryIndex(bucket.name(),\n                CreatePrimaryQueryIndexOptions.createPrimaryQueryIndexOptions().ignoreIfExists(true));\n    }\n\n    @ChangeSet(order = \"003\", id = \"someBasicIndes\", author = \"testAuthor\")\n    public void createSomeBasicIndes(Cluster cluster, Bucket bucket){\n        cluster.queryIndexes().createIndex(bucket.name(), \"nameIndex\", Arrays.asList(\"name\"),\n                CreateQueryIndexOptions.createQueryIndexOptions().ignoreIfExists(true));\n    }\n\n    @ChangeSet(order = \"004\", id = \"userPartialIndex\", author = \"testAuthor\")\n    public void createPartialIndex(Cluster cluster, Bucket bucket ){\n        cluster.query(\"CREATE INDEX user_idx ON `\"+bucket.name()+\"`(`_class`, `firstName`) WHERE (`_class` = '\"+ User.class.getName()+\"')\");\n    }\n\n    @ChangeSet(order = \"005\", id = \"copyNameToFirstName\", author = \"testAuthor\")\n    public void copyNameToFirstName(Cluster cluster, Bucket bucket){\n        cluster.query(\"update `\"+bucket.name()+\"` set firstName = name WHERE (`_class` = '\"+ User.class.getName()+\"')\");\n    }\n\n    @ChangeSet(order = \"006\", id = \"deleteUserName\", author = \"testAuthor\")\n    public void deleteUserName(Cluster cluster, Bucket bucket){\n        cluster.query(\" UPDATE `\"+bucket.name()+\"` UNSET name WHERE (`_class` = '\"+ User.class.getName()+\"')\");\n    }\n}\n\n```\n\n## Using Spring profiles\n     \n**CouchVersion** accepts Spring's `org.springframework.context.annotation.Profile` annotation. If a change log or change set class is annotated  with `@Profile`, \nthen it is activated for current application profiles.\n\n_Example 1_: annotated change set will be invoked for a `dev` profile\n```java\n@Profile(\"dev\")\n@ChangeSet(author = \"testuser\", id = \"myDevChangest\", order = \"01\")\npublic void devEnvOnly(DB db){\n  // ...\n}\n```\n_Example 2_: all change sets in a changelog will be invoked for a `test` profile\n```java\n@ChangeLog(order = \"1\")\n@Profile(\"test\")\npublic class ChangelogForTestEnv{\n  @ChangeSet(author = \"testuser\", id = \"myTestChangest\", order = \"01\")\n  public void testingEnvOnly(DB db){\n    // ...\n  } \n}\n```\n\n### Enabling @Profile annotation (optional)\n      \nTo enable the `@Profile` integration, please inject `org.springframework.context.ApplicationContext` to you runner.\n\n```java\n\n@Autowired\nprivate ApplicationContext context;\n\n@Bean\npublic CouchVersion couchversion() {\n  CouchVersion runner = new CouchVersion(context);\n  //... etc\n}\n```\n\n\n## Locks and Race Conditions\n\n**CouchVersion*** has an internal mechanism to avoid race conditions. Before running the migration, the framework will write a document with id **couchversion_lock** in the database to act as a lock.\nOther instances of the application will check this lock before trying to run the migration, and if the lock is present, they will sleep in exponential intervals until it reaches 5 minutes of waiting. After this time, if the lock has not been released yet, the application will fail to start.\n\nOnce the migration finishes or fails, the document with id **couchversion_lock** will be removed from the database.\n\n## Support\n\nIf you have any questions/requests, just ping me on twitter at [@deniswsrosa](https://twitter.com/deniswsrosa)\n\n## Special Thanks\n\nThis project is a fork of MongoBee, so thanks to all guys involved with it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcouchbaselabs%2Fcouchversion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcouchbaselabs%2Fcouchversion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcouchbaselabs%2Fcouchversion/lists"}