This example shows:
- How to setup Camunda, Spring Boot and various test frameworks correctly in order to work. It can be used as a copy & paste template.
- How to use AMQP and REST in your processes the Spring way.
- How to write proper scenario tests, that test if your process impacts the outer world as you expect (and not tests that Camunda did write a proper workflow engine).
- How this type of application can be easily deployed in the cloud (Pivotal Web Services as an example).
The business example is a very simple order fullfillment microservice (motivated by the flowing retail example):
- It is triggered via REST (it could have been an AMQP message or any other trigger as well, just REST is very easy to do demo requests)
- Calls a REST service (actually, the REST service is directly implemented here for simplicity, as I did not want to rely on an external URL)
- Sends a AMQP message and waits for a response. For simplicity, the request message will be directly treated as response
With Camunda it is possible to run the engine as part of your application or microservice. This is called embedded engine. This is especially helpful in microservice architectures if you develop in Java, then the engine simply gets a library helping you to define flows with persistent state and subsequent requirements like timeout handling, retrying, compensation and so on. See Why service collaboration needs choreography AND orchestration for background reading.
This video gives a very quick walk through the example (15 minutes):
The project uses
- Spring Boot
- Camunda
- camunda-bpm-spring-boot-starter
- camunda-bpm-assert-scenario
- camunda-bpm-assert
- camunda-bpm-process-test-coverage
- H2 for testing (in-memory) and running it locally (file based)
- PostgreSQL/ElephantDB as cloud storage on Pivotal Web Services
Please have a look at the pom.xml for details. Also note the Application.java as Spring Boot starter class.
With Spring Boot Camunda gets auto-configured using defaults. You can easily change the configuration by providing classes according to the docs. In this example you can see
- HistoryConfiguration that tells Camunda to save all historic data and audit logs.
- IdGenerator so that Camunda uses string UUID's instead of database generated ones, which avoids deadlock risks in cluster environments.
- Plugin to write some events to sysout. This plugin registers a listener to get notified when new workflow instances are started or existing ones are eneded. In this codebase just prints a line on the console, but it would be easy to push the event to some central tracing system. The cool thing: Such a plugin could be packaged in an own Maven depedency, as soon it is on the classpath it will be activated and influence the core engine.
The example uses the community edition to allow for a quick start. It is easy to switch dependencies to use the Enterprise Edition as you can see in this commit. Just make sure you can connect to the Camunda Nexus using your enterprise credentials.
One extremly interessting piece is the JUnit test case, which does a complete run-thorugh the process, including all Java code attached to the process, but without sending any real AMQP message or REST request. The timeout of a waiting period in the process is also simulated.
StartingByStarter starter = Scenario.run(orderProcess) //
.startBy(() -> {
return orderRestController.placeOrder(orderId, 547);
});
// expect the charge for retrieving payments to be created correctly and return a dummy transactionId
mockRestServer
.expect(requestTo("http://api.example.org:80/payment/charges")) //
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("amount").value("547"))
.andRespond(withSuccess("{\"transactionId\": \"12345\"}", MediaType.APPLICATION_JSON));
when(orderProcess.waitsAtReceiveTask("ReceiveTask_WaitForGoodsShipped")).thenReturn((messageSubscription) -> {
amqpReceiver.handleGoodsShippedEvent(orderId, "0815");
});
when(orderProcess.waitsAtTimerIntermediateEvent(anyString())).thenReturn((processInstance) -> {
processInstance.defer("PT10M", () -> {fail("Timer should have fired in the meanwhile");});
});
// OK - everything prepared - let's go
Scenario scenario = starter.execute();
mockRestServer.verify();
// and very that some things happened
assertThat(scenario.instance(orderProcess)).variables().containsEntry(ProcessConstants.VARIABLE_paymentTransactionId, "12345");
assertThat(scenario.instance(orderProcess)).variables().containsEntry(ProcessConstants.VAR_NAME_shipmentId, "0815");
{
ArgumentCaptor<Message> argument = ArgumentCaptor.forClass(Message.class);
verify(rabbitTemplate, times(1)).convertAndSend(eq("shipping"), eq("createShipment"), argument.capture());
assertEquals(orderId, argument.getValue());
}
verify(orderProcess).hasFinished("EndEvent_OrderShipped");
Refer to the OrderProcessTest.java for all details. Note that the test generates a graphical report:
In order to get started just
- Clone or download this example
- Maven build (this also runs the test cases)
mvn clean install
-
Install RabbitMQ and start it up
-
Run microservice via Java:
java -jar target/camunda-spring-boot-amqp-microservice-cloud-example-0.0.1-SNAPSHOT.jar
Now you can access:
curl --request POST -F 'orderId=1' -F 'amount=500' http://localhost:8080/order
Of course you can also use your favorite IDE.
You can easily deploy a Spring Boot application to various cloud providers, as you get a fat jar runnable on every JVM.
And using the Spring Cloud Connectors the application can be magically wired with cloud resources.
The example I show here is:
- Deployment on Pivotal Web Services
- ElephantSQL as hosted PostgreSQL, started as Service named
camunda-db
- CloudAMPQ as hosted RabbitMQ - started as Service
cloud-amqp
All metadata for the deployment are described in the manifest.yml:
---
applications:
- name: camunda-spring-boot-amqp-microservice-cloud-example
memory: 1G
instances: 1
random-route: false
services:
- cloud-amqp
- camunda-db
Now you can easily deploy the application using the CloudFoundry CLI. After logging in you can simply type:
mvn clean install && cf push -p target/camunda-spring-boot-amqp-microservice-cloud-example-0.0.1-SNAPSHOT.jar
There it is, now you can start a process:
url -X POST -F 'orderId=123' -F 'amount=4990' http://camunda-spring-boot-amqp-microservice-cloud-example.cfapps.io/order
And will see it in cockpit:
The URL to access the Camunda web applications and your REST-API depends on various factors, but will be shown via the Pivotal console: