Skip to content

Commit

Permalink
migrated example
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanlukas committed Feb 5, 2024
1 parent c1288b3 commit 19d9c38
Show file tree
Hide file tree
Showing 12 changed files with 682 additions and 0 deletions.
34 changes: 34 additions & 0 deletions c8-1-instance-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Instance Migration Workaround for Camunda 8

Currently, Camunda 8 does not support instance migration.
This prototype shows how we can use the Zeebe and Operate API to move process instances from one model version to another:
1. The process instances must have entered a wait state, such as a user task or a service task for which the external task worker has been deactivated.
2. For each instance of the old version, an instance in the new version is created.
* Variables are copied
* The instance is modified to start at a predefined point (i.e., the wait state)
3. The old instance is canceled.

This prototype considers the two latest versions of a model. Older versions are ignored.
This prototype is heavily inspired by the [Camunda 7 to 8 migration tooling](https://github.com/camunda-community-hub/camunda-7-to-8-migration).
## Configuration

Everything is preconfigured for local testing (i.e., localhost addresses without encryption).
You can configure the connection to Zeebe as described [here](https://github.com/camunda-community-hub/spring-zeebe).
You can furthermore use the following properties to configure the connection to Operate:
```
operate:
url: https://bru-2.operate.camunda.io/757dbc30-5127-4bed-XXXX-XXXXXXXXXXXX
# for self-managed setup configure
keycloak:
realm: camunda-platform
url: https://mykeycloak.example.com
```

## Version Information

This prototype has been build for Camunda 8.1.8.
It is compatible with both self-managed and SaaS deployments.

Camunda 8.2 provides additional API endpoints, which can be used to improve this prototype:
We can get the element ID of currently enabled flow nodes.
With this information, activity "wait until all processes reached waiting state" becomes obsolete.
34 changes: 34 additions & 0 deletions c8-1-instance-migration/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.camunda.consulting</groupId>
<artifactId>c8instancemigration</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.camunda</groupId>
<artifactId>spring-zeebe-starter</artifactId>
<version>8.1.13</version>
</dependency>
<dependency>
<groupId>io.camunda</groupId>
<artifactId>camunda-operate-client-java</artifactId>
<version>8.1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.7.7</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.camunda.consulting;

import io.camunda.operate.CamundaOperateClient;
import io.camunda.operate.dto.ProcessDefinition;
import io.camunda.operate.dto.ProcessInstance;
import io.camunda.operate.dto.ProcessInstanceState;
import io.camunda.operate.exception.OperateException;
import io.camunda.operate.search.ProcessDefinitionFilter;
import io.camunda.operate.search.ProcessInstanceFilter;
import io.camunda.operate.search.SearchQuery;
import io.camunda.operate.search.Sort;
import io.camunda.operate.search.SortOrder;
import io.camunda.operate.search.VariableFilter;
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
import io.camunda.zeebe.spring.client.EnableZeebeClient;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.annotation.Variable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
@EnableZeebeClient
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}

private static Logger LOG = LoggerFactory.getLogger(Main.class);

@Autowired
ZeebeClient client;
@Autowired
CamundaOperateClient operate;

@JobWorker
public Map<String, Object> fetchInstances(@Variable String bpmnProcessId) throws OperateException {
List<Long> processInstanceIds = new ArrayList<>();
ProcessDefinitionFilter filter = new ProcessDefinitionFilter.Builder().bpmnProcessId(bpmnProcessId).build();
Sort sort = new Sort("version", SortOrder.DESC);
SearchQuery query = new SearchQuery.Builder().filter(filter).sort(sort).build();
List<ProcessDefinition> definitions = operate.searchProcessDefinitions(query);
LOG.info("Found {} versions of process {}", definitions.size(), bpmnProcessId);
if (definitions.size() >= 2) {
ProcessInstanceFilter instanceFilter = new ProcessInstanceFilter.Builder().bpmnProcessId(bpmnProcessId).processVersion(definitions.get(1).getVersion()).state(
ProcessInstanceState.ACTIVE).build();
processInstanceIds = operate.searchProcessInstances(new SearchQuery.Builder().filter(instanceFilter).build()).stream()
.map(ProcessInstance::getKey).toList();
}
LOG.info("{} process instances will be migrated to latest version", processInstanceIds.size());
return Map.of("processInstanceKeys", processInstanceIds);
}

@JobWorker
public Map<String, Object> startInstance(@Variable Long processInstanceKey, @Variable String bpmnProcessId, @Variable String startBeforeElement)
throws OperateException {
Map<String, Object> variables = new HashMap<>();
VariableFilter filter = new VariableFilter.Builder().processInstanceKey(processInstanceKey).build();
List<io.camunda.operate.dto.Variable> vars = operate.searchVariables(new SearchQuery.Builder().filter(filter).build());
vars.forEach(var -> {
if (var.getTruncated()) {
try {
io.camunda.operate.dto.Variable fullVar = operate.getVariable(var.getKey());
variables.put(fullVar.getName(), fullVar.getValue());
} catch (OperateException e) {
throw new RuntimeException(e);
}
} else {
variables.put(var.getName(), var.getValue());
}
});
variables.put("oldInstanceKey", processInstanceKey);
ProcessInstanceEvent newInstance = client.newCreateInstanceCommand()
.bpmnProcessId(bpmnProcessId)
.latestVersion()
.startBeforeElement(startBeforeElement)
.variables(variables)
.send()
.join();
LOG.info("Migration of instance {} completed. New instance ID is {}.", processInstanceKey, newInstance.getProcessInstanceKey());
return Map.of("newInstanceKey", newInstance.getProcessInstanceKey());
}

@JobWorker
public void deleteOldInstance(@Variable Long processInstanceKey) {
client.newCancelInstanceCommand(processInstanceKey).send().join();
LOG.info("Instance {} has been cancelled, because it has been migrated to a new process model version.", processInstanceKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.camunda.consulting;

import io.camunda.operate.CamundaOperateClient;
import io.camunda.operate.auth.AuthInterface;
import io.camunda.operate.auth.SaasAuthentication;
import io.camunda.operate.auth.SelfManagedAuthentication;
import io.camunda.operate.auth.SimpleAuthentication;
import io.camunda.operate.exception.OperateException;
import io.camunda.zeebe.spring.client.annotation.Deployment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Objects;

@Configuration
@Deployment(resources = {"classpath:c8-to-c8-instance_migration.bpmn"})
public class OperateClientConfiguration {
@Value("${operate.keycloak.url:#{null}}")
String keycloakUrl;
@Value("${zeebe.client.cloud.client-id:#{null}}")
String clientId;

@Value("${zeebe.client.cloud.client-secret:#{null}}")
String clientSecret;

@Value("${operate.baseUrl:operate.camunda.io}")
String baseUrl;

@Value("${operate.authUrl:https://login.cloud.camunda.io/oauth/token}")
String authUrl;

@Value("${operate.url:http://localhost:8081}")
String operateUrl;

@Value("${operate.user:demo}")
String operateUsr;

@Value("${operate.password:demo}")
String password;

@Value("${operate.keycloak.realm:camunda-platform}")
String keycloakRealm;

@Bean
public CamundaOperateClient operate() {
AuthInterface auth = null;
if (Objects.nonNull(clientSecret) && Objects.nonNull(clientId)) {
if (Objects.nonNull(keycloakUrl)) {
auth = new SelfManagedAuthentication()
.clientId(clientId)
.clientSecret(clientSecret)
.keycloakRealm(keycloakRealm)
.keycloakUrl(keycloakUrl);
} else {
auth = new SaasAuthentication(authUrl, baseUrl, clientId, clientSecret);
}
} else {
auth = new SimpleAuthentication(operateUsr, password, operateUrl);
}
CamundaOperateClient client = null;
try {
client = new CamundaOperateClient.Builder().operateUrl(operateUrl)
.authentication(auth)
.build();
} catch (OperateException e) {
throw new RuntimeException(e);
}
return client;
}
}
3 changes: 3 additions & 0 deletions c8-1-instance-migration/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
zeebe.client:
broker.gateway-address: 127.0.0.1:26500
security.plaintext: true
Loading

0 comments on commit 19d9c38

Please sign in to comment.