Together with Rafael Cordones we took the existing Activiti Camel Module as a basis and did a huge refactoring. For everybody knowing this module I compiled the changes at the end of this article. For everybody else: Lean back and enjoy the show while I quickly walk you through the features. I do this based on our “camel use cases” example which is available on GitHub (by the way – you can discuss this process model on Camunda share):
The process does not solve any real-life business problem but showcases different use cases:
- Start a process instance when a file is dropped into a folder
- Start a process instance when a new Tweet is shared on Twitter with the term ‘Camunda’ in it
- Call a Camel route (“Service”) from a ServiceTask. This Service
always throws an Exception which we catch with a BPMN Error Event. - Call a Camel route (“Service”) from a SendTask and retrieve the response asynchronous in a following Message Event.
- Between all steps there is a UserTask so that you can click through the example using Tasklist.
The Camunda BPM Camel component
We developed a Camel component to talk to Camunda BPM from Camel. You can easily add this component to your own project as described here: https://github.com/camunda/camunda-bpm-camel. I currently work on a JBoss AS 7 distribution already containing Camel and this component – which would make it even more easy to get started. But that’s for another time. Let’s examine what the component can do for you:
Start a Process Instance
Starting a new process instance is really easy – just call the “camunda-bpm:start” endpoint within your Camel Route and specify which process definition should be used. In the example the File Component is used which can watch a drop folder for new files:
from("file://C:/temp/")
.convertBodyTo(String.class) // convert content into String
.to("camunda-bpm:start?processDefinitionKey=camel-use-cases");
As an alternative you can start a process instance by a message (then the message start event is triggered and the process definition has not to be specified). In the example the Twitter Component is used polling Twitter for new Tweets with the keyword ‘Camunda’:
from("twitter://search?type=polling&delay=5&keywords=camunda&consumerKey="...")
.transform().groovy("def map=[:] n" +
"map['tweet.what'] = request.body.text n" +
"map['tweet.when'] = request.body.createdAt n" +
"request.body = map")
.to("camunda-bpm:message?messageName=camel.start");
Note that I used a Groovy script to transform the data from what I get from Twitter to what I want to have in the process engine. As I am not a Groovy expert I am sure that there is a more elegant solution to do that – but it worked within minutes – so I was happy. Camel supports multiple possibilities to transform data easily .
Call a Camel Route from the Process Instance
In order to call a camel route you have to add a simple expression to your BPMN 2.0 process model:
In the background this will create the following XML:
<bpmn2:serviceTask id="ServiceTask_1" name="call some service"
camunda:expression="#{camel.sendTo('direct:syncService')}" >
Which references a Camel route:
from("direct://syncService")
.onException(SampleException.class) // map exception to BPMN error
.throwException(new BpmnError("camel.error"))
.end()
.process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
// always throwing error to demonstrate error event
throw new SampleException("some error occurred in service");
}
});
Get a response message
from("seda:someQueue")
.to("camunda-bpm:message?messageName=camel.answer");
In the example expect the property CamundaBpmProcessInstanceId to be present in the Camel message properties, this is how correlation currently is done. You could hook in some logic in your Route to do correlation/matching yourself, as e.g. shown in the Bank Account Opening Example (this uses by the way a ReceiveTask instead of a Message Event – both is possible):
from("file://" + postidentFolder)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
// extract key from file name
String businessKey =
exchange.getIn().getHeader("CamelFileName").toString()
.split("-")[1]
.substring(0, 4);
exchange.setProperty(CAMUNDA_BPM_BUSINESS_KEY, businessKey);
}
})
.to("camunda-bpm://message?activityId=wait_for_postident").
Where to get it?
- The camunda-bpm-camel component and further information can be found on GitHub: https://github.com/camunda/camunda-bpm-camel
- The sources for the example used in this blog post can be found on GitHub: https://github.com/camunda-consulting/code/tree/master/one-time-examples/camel-use-cases
- A business example (opening a bank account) using Camel can found on GitHub as well: https://github.com/camunda-consulting/code/tree/master/one-time-examples/bank-account-opening-camel
Note that camunda-bpm-camel is still in incubation phase!
Why is it cool?
Personally I like Apache Camel especially because it introduced a common language and a well adopted Component concept having a simple solution available for a lot of integration problems out there. File handling, Streaming, huge CSV files processes in parallel? No problem with Camel. Twitter, Salesforce, Hadoop or whatever? There might be a component, see this amazing list here: https://github.com/apache/camel/tree/master/components.
Camel is no rocket-science. It is not a complex ESB. It is a down-to-earth Java framework making your live easier and reducing the amount of code for quite some problems. The philosophy is matching pretty well with ours. Together with Camunda BPM you can already built some sophisticated BPM/SOA solution.
Further topics
- Correlation: We have some Correlation Mechanism already built in Camunda BPM. So we want to make that available in the Camel integration as well. Currently we discuss how this can be done best – basically which parameters to set in a Camel message in order to get that working. Join our forum to add your opinion!
- Retry Mechanism: Camel has some retry strategies if some external service is unavailable. Camunda BPM has own retry strategies as well. I recommend to use retrying from Camunda normally – as this makes monitoring easier. But I am happy to get feedback.
Ongoing discussions and next steps
- I would like to have Camel installed as a JBoss module and the route deployed within a Process Application. This should be easy doable – but currently we lack time for it.
- Should we rely on camel-cdi? As this has some drawbacks.
- Properties or Header for CamundaBpm properties? Currently we use properties. Properties are hanging off the Exchange and not off the messages that flow through a route. A processor in a route can copy the headers but may not do it. Properties are maintained all through the route. But normally headers are used. Maybe we should switch?
- Is it interesting to get a camunda-bpm://events?filter=… consumer endpoint that captures Audit Log events from the engine and send them down the route?
- Should we support multiple process engines? Ho do we expose this on the camel API? As a parameter (camunda-bpm://start?processEngine=…)?
Changes compared to Activiti Camel Module
And for everybody familiar with the Activiti Camel Module it might be interesting what we changed. In a nutshell:
- Removed the CamelBehavior (we prefer working with expressions, see examples above).
- Add support for Message Events (not only ReceiveTask).
- Separated the codebase to not force Spring dependencies when using CDI and vice-versa:
- Refactored the core code base to make it easier understandable.
- Added examples.