Skip to content

Commit

Permalink
Testing sample
Browse files Browse the repository at this point in the history
  • Loading branch information
aowss committed Dec 1, 2023
1 parent fa1f78f commit 5639421
Show file tree
Hide file tree
Showing 27 changed files with 840 additions and 72 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Maven Build

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2

- name: Set up JDK 21
uses: actions/setup-java@v1
with:
java-version: 21

- name: Check Dependencies
run: mvn -B dependency:analysis versions:display-dependency-updates --file pom.xml

- name: Unit Tests
run: mvn -B test --file pom.xml

- name: Build
run: mvn -B package --file pom.xml
71 changes: 68 additions & 3 deletions docs/Camunda.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
:figure-caption!:
:imagesdir: res
:toc2:

= Camunda 8

Expand Down Expand Up @@ -28,7 +29,7 @@ The clients communicate with the Zeebe cluster using https://grpc.io/[gRPC] thro

The https://docs.camunda.io/docs/components/console/introduction-to-console/[Console] component is used to manage the cluster.

* In the Self-Managed deployment option, Identity is the component that is in charge of authentication and authorization
* In the Self-Managed deployment option, https://docs.camunda.io/docs/self-managed/identity/what-is-identity/[Identity] is the component that is in charge of authentication and authorization

It is based on https://www.keycloak.org/[Keycloak].

Expand All @@ -49,12 +50,26 @@ It is based on https://www.keycloak.org/[Keycloak].
=== Development

* Use the latest https://spring.io/projects/spring-boot[Spring Boot] version: `3.2.X`

* Use the latest Java version: https://openjdk.org/projects/jdk/21/[JDK 21]

* Use https://github.com/camunda-community-hub/spring-zeebe[Spring Zeebe] to communicate with Zeebe from Spring Boot

* The worker's client credentials should be limited to Zeebe and Tasklist

Access to Operate and Optimize is not needed for the workers.

* The `handlers` package responsibilities are:

1. extract data from the `ActivatedJob` variables,
2. call the service,
3. push data back to the `ActivatedJob` variables,
4. inform the engine of the completion status

* The `services` package's sole responsibility is to communicate with our APIs

It should therefore have no dependency on any Camunda classes.

=== Deployment

=== Modeler
Expand All @@ -67,14 +82,64 @@ In particular, use Camunda's built-in https://docs.camunda.io/docs/components/be

* Follow Camunda's https://docs.camunda.io/docs/components/best-practices/modeling/naming-technically-relevant-ids/#using-naming-conventions-for-bpmn-ids[best practices] regarding ID names

This is very important because these names are needed in the tests and are logged by the engine and therefore needed for troubleshooting.

* Follow Camunda's https://docs.camunda.io/docs/components/best-practices/modeling/naming-bpmn-elements/[best practices] regarding BPMN element names

This helps in communicating with other stakeholders.

* Use camel case for service tasks' task definition
* Use camel case for

=== Tasklist

* As mentioned in https://academy.camunda.com/c8-technical-overview[Camunda 8 - Technical Overview], we should use the Tasklist https://docs.camunda.io/docs/apis-tools/tasklist-api-rest/tasklist-api-rest-overview/[REST API] over the https://docs.camunda.io/docs/apis-tools/tasklist-api/tasklist-api-overview/[GraphQL API] since the latter will be deprecated soon
* Use the Tasklist https://docs.camunda.io/docs/apis-tools/tasklist-api-rest/tasklist-api-rest-overview/[REST API] over the https://docs.camunda.io/docs/apis-tools/tasklist-api/tasklist-api-overview/[GraphQL API]

The GraphQL API will be deprecated soon as mentioned in https://academy.camunda.com/c8-technical-overview[Camunda 8 - Technical Overview].

=== Testing

* Run an embedded Zeebe test engine using the `zeebe-process-test-extension` dependency footnote:[as opposed to a test container using the `zeebe-process-test-extension-testcontainer` dependency]

* Use the Spring test dependency https://github.com/camunda-community-hub/spring-zeebe#writing-test-cases[`spring-zeebe-test`] which is a wrapper around `zeebe-process-test-extension`

* Use the assertions documented in https://docs.camunda.io/docs/apis-tools/java-client/zeebe-process-test/#assertions[Zeebe Process Test]

* Every path must be tested and in particular every FEEL expression

* For system tests, mock external APIs using https://wiremock.org[WireMock] instead of mocking the services using https://github.com/mockito/mockito[Mockito]

* For system tests, start the process from the start and avoid using the `startBeforeElement` API

* For unit tests, use https://github.com/mockito/mockito[Mockito] to mock the services and start the process just before the class under test is called using the `startBeforeElement` API

* Don't forget to wait in the tests using the `ZeebeTestEngine` wait methods: `waitForIdleState` & `waitForBusyState`

If you don't do that and use assertions about BPMN elements that are after the service or user tasks, the assertions will fail because they will be evaluated before the engine has time to progress in the flow.

== Questions

=== Testing

* How can we avoid redeploying the BPMN diagram before each test ?

[source, java]
----
@ZeebeProcessTest
public class ProcessTest {
private ZeebeClient client;
@BeforeEach
public void setup() {
DeploymentEvent deploymentEvent = client.newDeployResourceCommand()
.addResourceFromClasspath("process.bpmn")
.send()
.join();
}
}
----

Even though there is no need to redeploy the same BPMN diagram before each test, we can't use the `BeforeAll` annotation because it forces us to declare the `ZeebeClient` as `static`. +
As a consequence, the `ZeebeClient` instance is not injected correctly when using the `ZeebeProcessTest` annotation

== Resources

Expand Down
12 changes: 9 additions & 3 deletions service-orchestration/pom.xml → java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>camunda-service-orchestration</artifactId>
<artifactId>camunda-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-orchestration</name>
<packaging>jar</packaging>
<name>java</name>
<packaging>pom</packaging>

<modules>
<module>service-orchestration</module>
<module>testing</module>
</modules>

<dependencies>
<dependency>
Expand All @@ -21,4 +26,5 @@
<version>${camunda.version}</version>
</dependency>
</dependencies>

</project>
File renamed without changes.
17 changes: 17 additions & 0 deletions java/service-orchestration/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.micasa.tutorial</groupId>
<artifactId>camunda-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>camunda-java-service-orchestration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-orchestration</name>
<packaging>jar</packaging>

</project>
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package com.micasa.tutorial;

import com.micasa.tutorial.handler.CreditCardServiceHandler;
import com.micasa.tutorial.handler.CreditCardChargingHandler;
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.impl.oauth.OAuthCredentialsProvider;
import io.camunda.zeebe.client.impl.oauth.OAuthCredentialsProviderBuilder;

import java.time.Duration;
import java.util.Map;

public class PaymentApplication {

private static final String ZEEBE_ADDRESS = "4784612d-1495-4f2a-951d-049c8b985f06.ont-1.zeebe.camunda.io:443";
private static final String ZEEBE_CLIENT_ID = "jMNcIFp.GJ1-ZYK~Av-8zLHM7xmYCTLs";
private static final String ZEEBE_CLIENT_SECRET = "dbz--V.YX9JzRzF_44iL9DNz.h0_1S-qpPbl~2xYt6f0ua5h3CWQ2wVulQ68b-Rm";
private static final String ZEEBE_AUTHORIZATION_SERVER_URL = "https://login.cloud.camunda.io/oauth/token";
private static final String ZEEBE_TOKEN_AUDIENCE = "zeebe.camunda.io";
private static final String CAMUNDA_CLUSTER_ID = "4784612d-1495-4f2a-951d-049c8b985f06";
private static final String CAMUNDA_CLUSTER_REGION = "ont-1";
private static final String CAMUNDA_CREDENTIALS_SCOPES = "Zeebe";
private static final String CAMUNDA_OAUTH_URL = "https://login.cloud.camunda.io/oauth/token";

public static void main(String[] args) {

// Needed for the client to authenticate with the cluster
final var credentialsProvider = new OAuthCredentialsProviderBuilder()
.authorizationServerUrl(ZEEBE_AUTHORIZATION_SERVER_URL)
Expand All @@ -34,8 +31,8 @@ public static void main(String[] args) {
.build()) {

var variables = Map.of(
"reference", "C8_12345",
"amount", Double.valueOf(100.00),
"orderReference", "C8_12345",
"orderAmount", Double.valueOf(100.00),
"cardNumber", "1234567812345678",
"cardExpiry", "12/2023",
"cardCVC", "123"
Expand All @@ -49,18 +46,20 @@ public static void main(String[] args) {
.send()
.join();

// Register this job worker to handle 'chardCreditCard' jobs ( all jobs of this type will be assigned to this worker )
// Register & start this job worker to handle 'chardCreditCard' jobs ( all jobs of this type will be assigned to this worker )
var creditCardWorker = client.newWorker()
.jobType("chargeCreditCard")
.handler(new CreditCardServiceHandler()) // Uses the handler to process and complete the job
.handler(new CreditCardChargingHandler()) // Uses the handler to process and complete the job
.timeout(Duration.ofSeconds(10).toMillis()) // if the job is not completed quick enough, the worker releases the job which can then be picked up by another worker
.open();

Thread.sleep(10000);

creditCardWorker.close();

} catch (Exception e) {
e.printStackTrace();
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.micasa.tutorial.handler;

import com.micasa.tutorial.service.CreditCard;
import com.micasa.tutorial.service.CreditCardService;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.client.api.worker.JobHandler;

import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Map;

public class CreditCardChargingHandler implements JobHandler {

private final CreditCardService creditCardService;

public CreditCardChargingHandler(CreditCardService creditCardService) {
this.creditCardService = creditCardService;
}

public CreditCardChargingHandler() {
this(new CreditCardService());
}

@Override
public void handle(JobClient client, ActivatedJob job) throws Exception {
var reference = (String) job.getVariable("orderReference");
var amount = (Double) job.getVariable("orderAmount");
var creditCard = new CreditCard(
(String) job.getVariable("cardNumber"),
YearMonth.parse((String) job.getVariable("cardExpiry"), DateTimeFormatter.ofPattern("MM/yyyy")),
(String) job.getVariable("cardCVC")
);

var confirmationNumber = creditCardService.chargeCreditCard(reference, amount, creditCard);

// Inform Zeebe that the job was completed successfully and pass along the confirmation number
var outputVariables = Map.of("confirmation", confirmationNumber);
client.newCompleteCommand(job.getKey())
.variables(outputVariables)
.send()
.join();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import static java.lang.StringTemplate.STR;

public class CreditCardServiceMock {
public class CreditCardService {

public String chargeCreditCard(String transactionNumber, double amount, CreditCard creditCard) {
System.out.println(STR."Charging \{ amount } to credit card \{ creditCard} for transaction \{ transactionNumber }");
return UUID.randomUUID().toString();
}

}
3 changes: 3 additions & 0 deletions java/testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Service Orchestration

This is the implementation for [Camunda 8 - Testing Processes](https://academy.camunda.com/c8-testing-processes).
26 changes: 26 additions & 0 deletions java/testing/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.micasa.tutorial</groupId>
<artifactId>camunda-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>camunda-java-testing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>testing</name>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>io.camunda</groupId>
<artifactId>zeebe-process-test-extension</artifactId>
<version>${camunda.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Loading

0 comments on commit 5639421

Please sign in to comment.