Skip to main content

Invoking services from a Camunda 7 process

Camunda Platform 7 only

This best practice targets Camunda Platform 7.x only! If you are using Camunda Cloud, visit connecting the workflow engine with your world.

Access business logic implemented for the Java VM and remote services by means of small pieces of glue code. This glue code maps process input/output to your business logic by means of best-of-breed libraries of your own choosing.

In most cases, you should use a pull approach, where external worker threads query Camunda for external tasks. Sometimes, you might also attach JavaDelegates to your model, and in case you need to define totally self-contained BPMN process definitions, you may want to leverage scripts or expressions for small pieces of logic.

Understanding the possibilities​

Push and pull​

There are two patterns available to glue your code to a process model:

  • Push: The process engine actively issues a service call (or executes a script) via the mechanisms described below. The workflow engine pushes the work.
  • Pull: External worker threads query the process engine API for external tasks, and they pull the work. Then, they do the actual work and notify the process engine of works completion.

External tasks​

An external task is a task that waits to be completed by some external service worker without explicitly calling that service. It's configured by declaring a topic (which characterizes the type of the service). The Camunda API must be polled to retrieve open external tasks for a certain service's topic and must be informed about the completion of a task:

External task pattern

The interaction with the external task API can be done in two different ways:

Using external tasks comes with the following advantages:

  • Temporal decoupling: The pattern can replace a message queue between the service task (the "consumer") and the service implementation (the "provider"). It can eliminate the need for operating a dedicated message bus while keeping the decoupling that messaging would provide.

  • Polyglott architectures: The pattern can be used to integrate .NET based services, for example, when it might not be that easy to write Java delegates to call them. Service implementations are possible in any language that can be used to interact with a REST API.

  • Better scaling: The pattern allows you to start and stop workers as you like, and run as many of them as you need. By doing so, you can scale each service task (or to be precise, each "topic") individually.

  • Connect cloud and on-premises: The pattern supports you in running Camunda somewhere in the cloud (as our customers often do), because you can still have services on-premises, as they can now query their work via REST over SSL, which is also quite firewall-friendly.

  • Avoid timeouts: The pattern allows you to asynchronously call long-running services, which eventually block for hours (and would therefore cause transaction and connection timeouts when being called synchronously).

  • Run services on specialized hardware: Each worker can run in the environment that is best suited for the specific task of that worker; for example, CPU-optimized cloud instances for complex image processing and memory-optimized instances for other tasks.

Learn more about external tasks in the use guide as well as the reference and explore the video processing example shown above in greater detail by reading the blog post about it.

note

Camunda Cloud focuses on the external task pattern, there are no Java Delegates available as explained in this blog post.

Java Delegates​

A Java Delegate is a simple Java class that implements the Camunda JavaDelegate interface. It allows you to use dependency injection as long as it is constructed as a Spring or CDI bean and connected to your BPMN serviceTask via the camunda:delegateExpression attribute:

<serviceTask id="service_task_publish_on_twitter" camunda:delegateExpression="#{tweetPublicationDelegate}" name="Publish on Twitter">
</serviceTask>

Leverage dependency injection to get access to your business service beans from the delegate. Consider a delegate to be a semantical part of the process definition in a wider sense: it is taking care of the nuts and bolts needed to wire the business logic to your process. Typically, it does the following:

  1. Data Input Mapping
  2. Calling a method on the business service
  3. Data Output Mapping
note

Avoid programming business logic into Java Delegates. Separate this logic by calling one of your own classes as a business service, as shown below.

@Named
public class TweetPublicationDelegate implements JavaDelegate {

@Inject
private TweetPublicationService tweetPublicationService;

public void execute(DelegateExecution execution) throws Exception {
String tweet = new TwitterDemoProcessVariables(execution).getTweet(); // <1>
// ...
try {
tweetPublicationService.tweet(tweet); // <2>
} catch (DuplicateTweetException e) {
throw new BpmnError("duplicateMessage"); // <3>
}
}
//...
1

Retrieving the value of this process variable belongs to what we call the input mapping of the delegate code, and is therefore considered to be part of the wider process definition.

2

This method executes process engine-independent business logic. It is therefore not part of the wider process definition anymore and placed in a separate business service bean.

3

This exception is process engine-specific and therefore typically not produced by your business service method. It's part of the output mapping that we need to translate the business exception to the exception needed to drive the process - again code being part of the "wider" process definition and to be implemented in the Java Delegate.

In case you want to create Java Delegates that are reusable for other process definitions, leverage field injection to pass configuration from the BPMN process definition to your Java Delegate.

One advantage of using Java Delegates is that, if you develop in Java, this is a very simple way to write code and connect it with your process model, especially in embedded engine scenarios.

Selecting the implementation approach​

General recommendation​

In general, we recommend to use external tasks to apply a general architecture and mindset, that allows to leverage Camunda Cloud easier. This typically outweights the following downsides of external tasks:

  • A slightly increased complexity for Java projects, because they have to handle seperate Java clients.
  • A slightly increased overhead compared to Java Delegates, as all comunication with the engine is remote, even if it runs in the same Java VM.

Only if the increased latency does not work for your use case, for example, because you need to execute a 30-task process synchronously to generate a REST response within a handfull of milliseconds, should you then consider Java Delegates (or also consider switching to use Camunda Cloud).

Detailed comparison​

Java Delegate

Expression

Connector

External Task

Script Task

Named Bean

Java Class

Call a named bean or java class implementing theJavaDelegate interface.

Evaluate an expression using JUEL.

Use a configurable connector
(REST or SOAP services provided out-of-the-box).

Pull a service task into an external worker thread and inform process engine of completion.

Execute a script inside the engine.

Use with
BPMN elements.

task servicemessage intermediate sendtask send

task script

Communication Direction

Push
work item by issuing service call.

Pull
task from worker thread.

Push work item by executing a script.

Technology

Use your preferred framework, e.g. a JAX-WS client to call SOAP Web Services.

Use REST/SOAP Connector and Message Template

Use Camunda External Task Client or REST API to query for work.

Use JSR-223 compliant scripting engine.

Implement
via

Java (in same JVM)

Expression Language (can reference Java code)

BPMN configuration

BPMN configuration and external pull logic

E.g. Groovy, JavaScript, JRuby or Jython

Code Completion and Refactoring

βœ”

βœ”

Maybe

βœ”

Depends on language / IDE

Compiler Checks

βœ”

βœ”

βœ”

Depends on language / IDE

Dependency Injection

βœ”
(when using Spring, CDI, ...)

βœ”
(when using Spring, CDI, ...​)

Forces on Testing

Register mocks instead of original beans.

Mock business logic inside the JavaDelegate.

Register mocks instead of original beans.

Difficult because of lack of dependency injection.

Easy, as service is not actively called.

Consider external script resources.

Configure via

BPMN Attribute
serviceTask
camunda:
delegate
Expression

BPMN Attribute
serviceTask
camunda:
class

BPMN Attribute
serviceTask
camunda:
expression

BPMN Ext. Element+serviceTask
camunda:
connector

BPMN Attributes
serviceTask
camunda:
type=
'external' and
'camunda:topic'

BPMN Element
script or
BPMN Attribute
scriptTask
camunda:
resource

Fault Tolerance and Retrying

Handled by Camunda retry strategies and incident management.

Lock tasks for a defined time. Use Camunda’s retry and incident management.

Handled by Camunda retry strategies and incident management.

Scaling (having multiple Worker Threads)

Via load balancer in front of service

Multiple worker threads can be started.

Via job executor configuration

Throttling (e.g. one request at a time)

Not possible out-of-the-box, requires own throttling logic being implemented.

Start or stop exactly as many worker threads you need.

Not possible out-of-the-box.

Reusable Tasks

Use field injection

Use method parameters.

Build your own connector

Reuse external task topics and configure service via variables.

Use when

If external tasks do not work for your use case

Defining small pieces of logic directly in BPMN

Defining a self-contained BPMN process without Java code

Always if there is no reason against it

Defining BPMN processes without Java code.

Learn more

Learn more

Learn more

Learn more

Learn more

Dealing with problems and exceptions​

When invoking services, you can experience faults and exceptions. See our separate best practices about:

Example technology solutions​

Calling SOAP web services​

When you need to call a SOAP web service, you will typically be given access to a machine-readable, WSDL-based description of the service. You can then use JAX-WS and (for example) Apache CXF's JAX-WS client generation to generate a Java Web Service Client by making use of a Maven plugin. That client can be called from within your JavaDelegate.

Find a full example that uses JAX-WS client generation in the Camunda examples repository.

We typically prefer the client code generation over using the Camunda SOAP Connector, because of the better IDE support to do the data mapping by using code completion. You also can leverage standard testing approaches and changes in the WSDL will re-trigger code-generation and your compiler will check for any problems that arise from a changed interface. However, if you need a self-contained BPMN XML without any additional Java code, the connector could be the way to go. See SOAP connector example.

Calling REST web services​

If you need to call a REST web service, you will typically be given access to a human-readable documentation of the service. You can use standard Java REST client libraries like RestEasy or JAX-RS to write a Java REST service client that can be called from within a JavaDelegate.

We typically prefer writing Java clients over the Camunda REST Connector, because of the better IDE support to do the data mapping by using code completion. This way, you also can leverage standard testing approaches. However, if you need a self-contained BPMN XML without any additional Java code, the connector could be the way to go. See REST connector example.

Sending JMS messages​

When you need to send a JMS message, use a plain Java Client and invoke it from a service task in your process; for example, by using a Camunda Java Delegate:

@Named("jmsSender")
public class SendJmsMessageDelegate implements JavaDelegate {

@Resource(mappedName = "java:/queue/order")
private Queue queue;

@Resource(mappedName = "java:/JmsXA")
private QueueConnectionFactory connectionFactory;

public void execute(DelegateExecution execution) throws Exception {
String correlationId = UUID.randomUUID().toString(); // <1>
execution.setVariable("jmsCorrelationId", correlationId);

Connection connection = connectionFactory.createConnection(); // <2>
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);

TextMessage message = session.createTextMessage( // <3>
"someOwnContent, e.g. Tweet Object Data, plus " + correlationId); // <4>
producer.send(message);

producer.close();
session.close();
connection.close();
}

}
1

Consider what information you can use to correlate back an asynchronous response to your process instance. We typically prefer a generated, artificial UUID for communication, which the waiting process will also need to remember.

2

You will need to open and close JMS connections, sessions, and producers. Note that this example just serves to get you started. In real life, you will need to decide which connections you need to open, and of course, properly close.

3

You will need to create and send your specific message.

4

Add relevant business data to your message together with correlation information.

danger

This example just serves to get you started. In real life, consider whether you need to encapsulate the JMS client in a separate class and just wire it from the Java Delegate. Also decide which connections you need to open and close properly at which peristaltic points.

On GitHub, you can find a more complete example for asynchronous messaging with JMS.

Using SQL to access the database​

Use plain JDBC if you have simple requirements. Invoke your SQL statement from a service task in your process; for example, by using a Camunda Java Delegate:

@Named("simpleSqlDelegate")
public class simpleSqlDelegate implements JavaDelegate {

@Resource(name="customerDB")
private javax.sql.DataSource customerDB;

public void execute(DelegateExecution execution) throws Exception {
Statement statement = null;
Connection connection = null;

try {
connection = customerDB.getConnection();
String query = "SELECT name " + // <1>
"FROM customer " +
"WHERE id = ?";
statement = connection.createStatement();
statement.setString(1, execution.getProcessBusinessKey()); // <2>
ResultSet resultSet = stmt.executeQuery(query);
if (resultSet.next()) {
execution.setVariable("customerName", resultSet.getString("name")); // <3>
}
} finally {
if (statement != null) statement.close();
if (connection != null) connection.close();
}

}
1

You will need to define your SQL statement. Consider using prepared statements if you want to execute a statement object many times.

2

You will typically need to feed parameters into your SQL query that are already known during execution of the process instance...

3

...and deliver back a potential result that maybe needed later in the process.

danger

This example just serves to get you started. For real life, consider whether you need to encapsulate the JDBC code in a separate class and just wire it from the Java Delegate. Also decide which connections you need to open and close properly at which point.

Note that the Camunda process engine will have opened a database transaction for its own persistence purposes when calling the Java Delegate shown above. You will need to make a conscious decision if you want to join that transaction (and setup your TX management accordingly).

Instead of invoking SQL directly, consider using JPA if you have more complex requirements. Its object/relational mapping techniques will allow you to bind database tables to Java objects and abstract from specific database vendors and their specific SQL dialects.

Calling SAP systems​

To call a SAP system, you have the following options:

  • Use REST or SOAP client calls, connecting Camunda to SAP Netweaver Gateway or SAP Enterprise Services.

  • Use SAP's Java Connectors (JCo). Consider using some frameworks to make this easier, like the open-source frameworks of Hibersap.

Executing a Groovy script​

A script task...

Β ...is defined by specifying the script and the scriptFormat.

<scriptTask id='theScriptTask' scriptFormat='groovy' camunda:resultVariable="size">
<script>anArray.size()</script>
</scriptTask>

For more extensive code (which should also be tested separately), consider using scripts external to your BPMN file and reference them with a camunda:resource attribute on the scriptTask.

Learn more about the many ways scripts can be used with Camunda from our user guide.