Skip to main content
Back to Blog
3 January 202614 min read

Camunda Platform 8: BPMN and Process Orchestration

CamundaBPMNProcess AutomationWorkflow

A comprehensive guide to building business process automation with Camunda Platform 8. BPMN 2.0 modeling, Zeebe engine architecture, and cloud-native process orchestration.


Camunda Platform 8: BPMN and Process Orchestration

Process orchestration is essential for modern enterprises managing complex workflows across distributed systems. Camunda Platform 8 represents a fundamental shift from traditional BPM engines to cloud-native process orchestration. Here's what I've learned implementing Camunda across enterprise projects.

Why Process Orchestration Matters

In microservices architectures, coordinating business processes across multiple services becomes increasingly complex. Without proper orchestration:

  • Visibility gaps: No clear view of process state across services
  • Error handling complexity: Each service handles failures differently
  • Audit challenges: Reconstructing process history from distributed logs
  • Coordination overhead: Manual tracking of multi-step operations

Camunda Platform 8 Architecture

Zeebe: The Process Engine

Camunda 8 is built on Zeebe, a horizontally scalable workflow engine:

Camunda Platform 8 Architecture:

┌─────────────────────────────────────────────────────────────┐
│                      Camunda Cloud                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │   Zeebe     │  │   Zeebe     │  │   Zeebe     │        │
│  │  Broker 1   │  │  Broker 2   │  │  Broker 3   │        │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘        │
│         │                │                │                │
│         └────────────────┼────────────────┘                │
│                          │                                 │
│  ┌───────────────────────▼───────────────────────┐        │
│  │              Zeebe Gateway                     │        │
│  │         (gRPC API / REST API)                 │        │
│  └───────────────────────┬───────────────────────┘        │
│                          │                                 │
├──────────────────────────┼──────────────────────────────────┤
│                          │                                 │
│  ┌───────────┐  ┌───────▼───────┐  ┌─────────────┐        │
│  │  Operate  │  │   Tasklist    │  │  Optimize   │        │
│  │(Monitoring)│  │(Human Tasks) │  │ (Analytics) │        │
│  └───────────┘  └───────────────┘  └─────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                          │
         ┌────────────────┼────────────────┐
         │                │                │
    ┌────▼────┐     ┌────▼────┐     ┌────▼────┐
    │ Worker  │     │ Worker  │     │ Worker  │
    │Service 1│     │Service 2│     │Service 3│
    └─────────┘     └─────────┘     └─────────┘

Key Architectural Differences from Camunda 7

AspectCamunda 7Camunda 8
EngineEmbedded Java engineDistributed Zeebe brokers
ScalingVertical (larger JVM)Horizontal (add brokers)
DatabaseRDBMS (PostgreSQL, etc.)Append-only log + Elasticsearch
CommunicationREST/Java APIgRPC (primary), REST (gateway)
ExecutionSynchronousAsynchronous job workers
DeploymentSelf-managedCloud-native / Self-managed

BPMN 2.0 Fundamentals

Core BPMN Elements

BPMN Element Categories:

Events (Circles):
  ○ Start Event      - Process entry point
  ◎ Intermediate     - Mid-process events
  ● End Event        - Process completion

Activities (Rounded Rectangles):
  ┌─────────┐
  │  Task   │        - Single unit of work
  └─────────┘
  ┌─────────┐
  │ ═══════ │        - Sub-process (embedded)
  │  Task   │
  └─────────┘

Gateways (Diamonds):
  ◇ Exclusive (XOR)  - One path based on condition
  ◆ Parallel (AND)   - All paths execute
  ○◇ Inclusive (OR)  - One or more paths
  ◇→ Event-based     - Wait for first event

Sequence Flows:
  ────────→          - Normal flow
  ─ ─ ─ ─→          - Conditional flow

Practical BPMN Example: Order Processing

<?xml version="1.0" encoding="UTF-8"?> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0"> <bpmn:process id="order-processing" isExecutable="true"> <!-- Start Event --> <bpmn:startEvent id="order-received" name="Order Received"> <bpmn:outgoing>flow1</bpmn:outgoing> </bpmn:startEvent> <!-- Validate Order --> <bpmn:serviceTask id="validate-order" name="Validate Order"> <bpmn:extensionElements> <zeebe:taskDefinition type="validate-order" /> </bpmn:extensionElements> <bpmn:incoming>flow1</bpmn:incoming> <bpmn:outgoing>flow2</bpmn:outgoing> </bpmn:serviceTask> <!-- Decision Gateway --> <bpmn:exclusiveGateway id="validation-result" name="Valid?"> <bpmn:incoming>flow2</bpmn:incoming> <bpmn:outgoing>flow-valid</bpmn:outgoing> <bpmn:outgoing>flow-invalid</bpmn:outgoing> </bpmn:exclusiveGateway> <!-- Parallel Gateway - Process Order --> <bpmn:parallelGateway id="parallel-start"> <bpmn:incoming>flow-valid</bpmn:incoming> <bpmn:outgoing>flow-payment</bpmn:outgoing> <bpmn:outgoing>flow-inventory</bpmn:outgoing> </bpmn:parallelGateway> <!-- Process Payment --> <bpmn:serviceTask id="process-payment" name="Process Payment"> <bpmn:extensionElements> <zeebe:taskDefinition type="process-payment" retries="3" /> </bpmn:extensionElements> <bpmn:incoming>flow-payment</bpmn:incoming> <bpmn:outgoing>flow-payment-done</bpmn:outgoing> </bpmn:serviceTask> <!-- Reserve Inventory --> <bpmn:serviceTask id="reserve-inventory" name="Reserve Inventory"> <bpmn:extensionElements> <zeebe:taskDefinition type="reserve-inventory" retries="3" /> </bpmn:extensionElements> <bpmn:incoming>flow-inventory</bpmn:incoming> <bpmn:outgoing>flow-inventory-done</bpmn:outgoing> </bpmn:serviceTask> <!-- Join Parallel --> <bpmn:parallelGateway id="parallel-join"> <bpmn:incoming>flow-payment-done</bpmn:incoming> <bpmn:incoming>flow-inventory-done</bpmn:incoming> <bpmn:outgoing>flow-to-ship</bpmn:outgoing> </bpmn:parallelGateway> <!-- Ship Order --> <bpmn:serviceTask id="ship-order" name="Ship Order"> <bpmn:extensionElements> <zeebe:taskDefinition type="ship-order" /> </bpmn:extensionElements> <bpmn:incoming>flow-to-ship</bpmn:incoming> <bpmn:outgoing>flow-to-end</bpmn:outgoing> </bpmn:serviceTask> <!-- End Events --> <bpmn:endEvent id="order-completed" name="Order Completed"> <bpmn:incoming>flow-to-end</bpmn:incoming> </bpmn:endEvent> <bpmn:endEvent id="order-rejected" name="Order Rejected"> <bpmn:incoming>flow-invalid</bpmn:incoming> </bpmn:endEvent> </bpmn:process> </bpmn:definitions>

Building Job Workers

TypeScript Worker Implementation

import { ZBClient } from 'zeebe-node'; interface OrderVariables { orderId: string; customerId: string; items: Array<{ sku: string; quantity: number; price: number }>; totalAmount: number; } interface ValidationResult { isValid: boolean; validationErrors: string[]; } // Create Zeebe client const zbc = new ZBClient({ camundaCloud: { clusterId: process.env.ZEEBE_CLUSTER_ID!, clientId: process.env.ZEEBE_CLIENT_ID!, clientSecret: process.env.ZEEBE_CLIENT_SECRET!, }, }); // Validate Order Worker zbc.createWorker<OrderVariables, ValidationResult>({ taskType: 'validate-order', taskHandler: async (job) => { const { orderId, items, totalAmount } = job.variables; console.log(`Validating order ${orderId}`); const errors: string[] = []; // Validate items if (!items || items.length === 0) { errors.push('Order must contain at least one item'); } // Validate amounts const calculatedTotal = items?.reduce( (sum, item) => sum + item.price * item.quantity, 0 ) || 0; if (Math.abs(calculatedTotal - totalAmount) > 0.01) { errors.push('Order total does not match item prices'); } // Validate inventory availability (external call) for (const item of items || []) { const available = await checkInventory(item.sku, item.quantity); if (!available) { errors.push(`Item ${item.sku} not available in requested quantity`); } } return job.complete({ isValid: errors.length === 0, validationErrors: errors, }); }, }); // Process Payment Worker zbc.createWorker<OrderVariables & { paymentMethod: string }>({ taskType: 'process-payment', taskHandler: async (job) => { const { orderId, totalAmount, customerId, paymentMethod } = job.variables; console.log(`Processing payment for order ${orderId}`); try { const paymentResult = await paymentService.charge({ customerId, amount: totalAmount, currency: 'GBP', method: paymentMethod, orderId, }); return job.complete({ paymentId: paymentResult.transactionId, paymentStatus: 'completed', }); } catch (error) { // Fail the job - Zeebe will retry based on retries config return job.fail(`Payment failed: ${error.message}`); } }, }); // Reserve Inventory Worker zbc.createWorker<OrderVariables>({ taskType: 'reserve-inventory', taskHandler: async (job) => { const { orderId, items } = job.variables; console.log(`Reserving inventory for order ${orderId}`); const reservations = await Promise.all( items.map(item => inventoryService.reserve({ sku: item.sku, quantity: item.quantity, orderId, }) ) ); return job.complete({ reservationIds: reservations.map(r => r.id), }); }, }); // Ship Order Worker zbc.createWorker<OrderVariables & { reservationIds: string[] }>({ taskType: 'ship-order', taskHandler: async (job) => { const { orderId, customerId } = job.variables; console.log(`Creating shipment for order ${orderId}`); const shipment = await shippingService.createShipment({ orderId, customerId, items: job.variables.items, }); // Send notification await notificationService.send({ type: 'ORDER_SHIPPED', customerId, data: { orderId, trackingNumber: shipment.trackingNumber, estimatedDelivery: shipment.estimatedDelivery, }, }); return job.complete({ shipmentId: shipment.id, trackingNumber: shipment.trackingNumber, }); }, });

Worker Best Practices

// Idempotent worker design zbc.createWorker({ taskType: 'process-payment', taskHandler: async (job) => { const { orderId } = job.variables; // Check if already processed (idempotency) const existing = await paymentRepo.findByOrderId(orderId); if (existing) { console.log(`Payment already processed for order ${orderId}`); return job.complete({ paymentId: existing.id, paymentStatus: existing.status, }); } // Process new payment // ... }, // Worker configuration maxJobsToActivate: 32, // Batch size timeout: Duration.seconds.of(30), // Job timeout pollInterval: Duration.milliseconds.of(300), });

Starting Process Instances

Via REST API

// Start process instance const processInstance = await zbc.createProcessInstance({ bpmnProcessId: 'order-processing', variables: { orderId: 'ORD-12345', customerId: 'CUST-789', items: [ { sku: 'WIDGET-001', quantity: 2, price: 29.99 }, { sku: 'GADGET-002', quantity: 1, price: 49.99 }, ], totalAmount: 109.97, paymentMethod: 'card', }, }); console.log(`Started process instance: ${processInstance.processInstanceKey}`);

With Message Correlation

// Start via message (event-driven) await zbc.publishMessage({ name: 'order-received', correlationKey: 'ORD-12345', variables: { orderId: 'ORD-12345', // ... order data }, timeToLive: Duration.seconds.of(60), }); // Correlate message to running instance await zbc.publishMessage({ name: 'payment-callback', correlationKey: 'ORD-12345', // Matches running instance variables: { paymentStatus: 'confirmed', transactionId: 'TXN-ABC123', }, });

Error Handling and Compensation

BPMN Error Events

// Throw BPMN error for business exceptions zbc.createWorker({ taskType: 'process-payment', taskHandler: async (job) => { try { const result = await paymentService.charge(/* ... */); return job.complete({ paymentId: result.id }); } catch (error) { if (error.code === 'INSUFFICIENT_FUNDS') { // BPMN error - caught by error boundary event return job.error('PAYMENT_FAILED', 'Insufficient funds'); } if (error.code === 'CARD_DECLINED') { return job.error('PAYMENT_DECLINED', 'Card was declined'); } // Technical failure - retry return job.fail(`Payment service error: ${error.message}`); } }, });

Compensation Pattern

Saga Pattern with Compensation:

    ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
    │   Reserve    │────▶│   Charge     │────▶│    Ship      │
    │  Inventory   │     │   Payment    │     │    Order     │
    └──────────────┘     └──────────────┘     └──────────────┘
           │                    │                    │
           │ Compensate         │ Compensate         │
           ▼                    ▼                    ▼
    ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
    │   Release    │◀────│   Refund     │◀────│   Cancel     │
    │  Inventory   │     │   Payment    │     │   Shipment   │
    └──────────────┘     └──────────────┘     └──────────────┘

Monitoring with Operate

Key Metrics to Track

MetricDescriptionAlert Threshold
Active InstancesCurrently running processes> 10,000
Incident RateFailed jobs / Total jobs> 5%
Cycle TimeStart to end duration> SLA
Job LatencyTime in queue before pickup> 30s
Worker ThroughputJobs completed per minute< baseline

Operate Dashboard Integration

// Custom metrics for Prometheus import { Counter, Histogram } from 'prom-client'; const processStarted = new Counter({ name: 'camunda_process_started_total', help: 'Total processes started', labelNames: ['process_id'], }); const taskDuration = new Histogram({ name: 'camunda_task_duration_seconds', help: 'Task execution duration', labelNames: ['task_type'], buckets: [0.1, 0.5, 1, 2, 5, 10, 30], }); // Instrument workers zbc.createWorker({ taskType: 'validate-order', taskHandler: async (job) => { const timer = taskDuration.startTimer({ task_type: 'validate-order' }); try { // ... task logic return job.complete(result); } finally { timer(); } }, });

Key Takeaways

  1. Zeebe is fundamentally different: Async job workers replace synchronous execution—design accordingly

  2. Horizontal scaling: Add brokers and workers independently based on load

  3. Idempotency is critical: Workers may receive the same job multiple times—design for replay

  4. Error handling strategy: Distinguish between technical failures (retry) and business errors (BPMN error events)

  5. Compensation over rollback: Implement saga patterns for distributed transactions

  6. Observability first: Instrument workers from day one—Operate provides visibility but custom metrics add depth

Camunda Platform 8 represents a paradigm shift in process orchestration. The move to cloud-native architecture unlocks horizontal scalability but requires rethinking how you design and implement process automation. \

Share this article