Camunda Saga Orchestration with YAML: A Real-World E-Commerce Example

Distributed microservices often face the challenge of maintaining data consistency without relying on heavy distributed transactions. The SAGA design pattern is a proven solution: it breaks a long transaction into smaller steps, each with a compensating action if something fails.

While Camunda traditionally relies on BPMN XML files, developers can simplify configuration by using YAML-based workflow definitions. YAML is easier to read and maintain, while still being fully compatible with Camunda’s orchestration engine.

In this blog, we’ll see how to implement a real-world e-commerce order processing saga in Camunda using YAML workflows, complete with compensating transactions.


Why Use Camunda for SAGA Orchestration?

Centralized Orchestration – Camunda acts as a Saga orchestrator across multiple microservices.

Compensation Handling – Built-in support for rollback actions.

Asynchronous Messaging – Works seamlessly with REST, Kafka, and RabbitMQ.

Monitoring – Camunda Cockpit provides real-time tracking of Saga states.

Retries & Timeouts – Configurable per task to ensure robustness.


Real-World Example: E-Commerce Order Saga

Our order processing workflow:

1. Reserve Inventory

2. Process Payment

3. Arrange Shipment


If any step fails, compensating actions are triggered:

Cancel Inventory Reservation

Refund Payment

Cancel Shipment


YAML Workflow Definition for the Saga

Instead of writing verbose XML, we define our process in YAML:


process:

  id: orderSaga

  isExecutable: true

  tasks:

    - id: ReserveInventory

      type: service

      delegate: reserveInventoryDelegate

      error: INVENTORY_FAILED

      compensation: CancelInventory


    - id: ProcessPayment

      type: service

      delegate: processPaymentDelegate

      error: PAYMENT_FAILED

      compensation: RefundPayment


    - id: ArrangeShipment

      type: service

      delegate: arrangeShipmentDelegate

      error: SHIPMENT_FAILED

      compensation: CancelShipment


This YAML is automatically converted into BPMN XML under the hood during deployment.


YAML-to-BPMN Conversion Setup

To use YAML instead of XML in Camunda, you can leverage community plugins and converters:


1. Camunda Modeler YAML Plugin

Allows defining workflows in YAML and converts them to BPMN.


GitHub Project: camunda-yaml-model


2. Maven Plugin Integration

Configure Maven to run YAML → BPMN conversion before packaging your app.

<plugin>

  <groupId>io.holunda</groupId>

  <artifactId>camunda-bpm-yaml-maven-plugin</artifactId>

  <version>1.0.0</version>

  <executions>

    <execution>

      <phase>generate-resources</phase>

      <goals>

        <goal>convert</goal>

      </goals>

    </execution>

  </executions>

</plugin>


3. Deployment

Place YAML files under src/main/resources/bpmn/.

During build, they’ll be converted to .bpmn XML and deployed to Camunda automatically.

This way, you write YAML, but Camunda still consumes standard BPMN XML behind the scenes.


Java Delegates for Local Transactions

Each step is implemented as a delegate with compensating logic:


@Component("reserveInventoryDelegate")

public class ReserveInventoryDelegate implements JavaDelegate {

    @Autowired private InventoryService inventoryService;

    @Override

    public void execute(DelegateExecution execution) {

        String orderId = execution.getBusinessKey();

        if (!inventoryService.reserve(orderId)) {

            throw new BpmnError("INVENTORY_FAILED");

        }

    }

}


@Component("cancelInventoryDelegate")

public class CancelInventoryDelegate implements JavaDelegate {

    @Autowired private InventoryService inventoryService;

    @Override

    public void execute(DelegateExecution execution) {

        inventoryService.cancelReservation(execution.getBusinessKey());

    }

}


Similarly, implement Payment and Shipment delegates with compensating actions.


Starting the Saga from a REST Controller


@RestController

@RequestMapping("/orders")

public class OrderController {

    @Autowired private RuntimeService runtimeService;


    @PostMapping("/place")

    public String placeOrder(@RequestParam String orderId) {

        ProcessInstance instance = runtimeService.startProcessInstanceByKey(

            "orderSaga", orderId

        );

        return "Order saga started with ID: " + instance.getProcessInstanceId();

    }

}


Best Practices When Using Camunda with YAML


1. Match Error Codes Correctly

The error in YAML must exactly match the BpmnError thrown in code.

2. Idempotent Compensations

Ensure cancel and refund actions can run multiple times safely.

3. Use Business Keys

Always pass unique identifiers (like orderId) to correlate saga instances.

4. Configure Retries & Timeouts

Define retry cycles for transient errors (e.g., network outages).

5. Monitor in Camunda Cockpit

Use Cockpit to check active/failed sagas and compensation history.

6. Process Versioning

Deploying new YAML creates new BPMN versions—always test compensations in staging.


Conclusion

By using Camunda with YAML configurations, you simplify the way you define and manage complex distributed workflows. In our e-commerce order saga example, YAML made the orchestration much easier to maintain while ensuring data consistency across microservices.

With the right practices—error handling, retries, idempotency, and monitoring—you can confidently implement the SAGA pattern in production-ready systems using Camunda.

Post a Comment

0 Comments