Migrating Camunda 7 External Tasks
Camunda 7 external tasks let workers pull work over REST: a worker calls fetchAndLock
for a topic, does the job, then calls complete, failure or bpmnError. The
Camunda external-task plugin from the
fleans-plugins repository serves the same REST API on top of Fleans. You can move your processes to Fleans and
keep your workers. In most cases you only change their base URL.
The engine has no Camunda-specific code. The plugin is an ordinary
custom-task worker that connects to the API over gRPC.
It subscribes to camunda:*, stores waiting tasks in its own table,
defers them, and reports workers’ results
back through the REST API.
Plugin 0.11.0 requires Fleans 0.11.0 or later.
Upgrading from 0.9.x: the engine now evaluates the input/output mappings, and the plugin’s
camunda_external_tasks table changed shape. Drain the waiting tasks, drop the table (the plugin
recreates it at startup), then upgrade the engine and the plugin together.
1. Adapt the BPMN
Section titled “1. Adapt the BPMN”Replace Camunda’s camunda:type="external" camunda:topic="…" attribute pair with a Fleans
task type that starts with camunda::
<!-- Camunda 7 --><bpmn:serviceTask id="charge" camunda:type="external" camunda:topic="charge-card" />
<!-- Fleans --><bpmn:serviceTask id="charge" type="camunda:charge-card" />Workers keep subscribing to the topic (charge-card). Camunda’s <camunda:inputOutput> is not
read. Use Fleans input/output mappings instead;
the plugin README
has a conversion table.
The task’s businessKey is the process instance’s business id,
set when you start the instance ("BusinessId": "order-123" on POST /Execution/start or
POST /Execution/message). You don’t need a mapping for it.
Other Camunda task metadata comes from reserved input targets, evaluated when the task is queued (except extension properties, evaluated at fetch time) and never sent as variables:
<zeebe:ioMapping> <zeebe:input source="=order.priority" target="taskPriority" /> <!-- camunda:taskPriority --> <zeebe:input source="acme" target="tenantId" /> <zeebe:input source="eu-west" target="extensionProperty_region" /> <!-- camunda:properties --></zeebe:ioMapping>A businessKey input replaces the business id, even when it evaluates to null. Use it where
the instance has no business id, such as a call activity’s child instance:
<zeebe:input source="=order.id" target="businessKey" />.
2. Run the plugin
Section titled “2. Run the plugin”The plugin repository ships one ready-to-run worker image with every plugin in it,
ghcr.io/nightbaker/fleans-plugins. It needs the API’s URL and a database for its table — the
single-PostgreSQL and
scalable installs show it in their compose files. With the
Docker Compose bundle:
Start the engine first. The bundle creates the fleans_default network that the plugin
joins, and the network only gets that name when the compose project is called fleans:
cd <unzipped-engine-compose-bundle>echo "COMPOSE_PROJECT_NAME=fleans" >> .env # names the network fleans_defaultdocker compose up -d # engine first: creates the networkThen add a docker-compose.override.yaml next to the bundle’s docker-compose.yaml:
services: fleans-plugins: image: ghcr.io/nightbaker/fleans-plugins:0.11.0 networks: [fleans_default] ports: ["8090:8080"] environment: Fleans__ExternalTaskWorker__EngineUrl: "http://fleans-core:8080" ConnectionStrings__camunda-external-tasks: "Host=postgres;Database=camunda_external_tasks;Username=postgres;Password=${POSTGRES_PASSWORD}" depends_on: [fleans-core, postgres]networks: fleans_default: external: true name: fleans_defaultThen run docker compose up -d again from the bundle directory. Compose merges the override and
starts the plugin on the existing network. If you skip the engine-first step or the project
name, it fails with network fleans_default declared as external, but could not be found.
The database and the camunda_external_tasks table are created at startup.
To run the plugin inside a worker you already have, add the
Fleans.Plugins.CamundaExternalTask NuGet package and register it with
builder.AddCamundaExternalTaskPlugin() — one line that reads its settings and maps the API. See the
plugin guide for
details and every configuration setting.
3. Point the workers at the plugin
Section titled “3. Point the workers at the plugin”ExternalTaskClient.create().baseUrl("http://camunda-external-task:8080/engine-rest").build();new Client({ baseUrl: "http://camunda-external-task:8080/engine-rest" });Every endpoint the official Java and JS clients call is supported: fetchAndLock (including
long polling, usePriority, sorting, and the businessKey / tenant filters), complete,
failure, bpmnError, lock, extendLock, unlock, and the file-variable download. The
operator endpoints are there too: task queries and counts, topic-names, PUT /retries and
PUT /priority. Errors use Camunda’s body and status codes. Set
CamundaExternalTasks__BasicAuth__Username and __Password on the plugin to require HTTP basic
authentication (the clients’ BasicAuthProvider / BasicAuthInterceptor).
Differences from Camunda 7
Section titled “Differences from Camunda 7”- Incidents are opt-in. By default,
failurewithretries = 0fails the activity with error code500, and an error boundary event can catch it. WithCamundaExternalTasks__Incidents=truethe task stays as an incident, as in Camunda, untilPUT /external-task/{id}/retriesresolves it. Incidents are not shown in the Fleans UI. bpmnErrorfails the activity with the worker’serrorCode, so an error boundary event with that code catches it. Its variables are visible to the boundary or error event sub-process that catches it, but not to an error boundary on a parent workflow’s call activity.- Variables: primitives,
Date(kept as a string),Json,Bytes,File, andObjectwithapplication/jsonare supported. Binary values live in the workflow state, so keep them small. Java-serialized objects are rejected. - Missing fields:
processDefinitionVersionTagis alwaysnull, because Fleans has no equivalent. - Scope: the
/external-taskresource plus the file-variable download. Start processes and correlate messages through the Fleans REST API.
The plugin guide lists every difference.