Camunda Team Blog

Bringing Together: Transactions, Cancel Events and Compensation Tasks

Written by Niall Deehan on , under Execution category.
If you've ever been lucky enough to enjoy Camunda's BPMN training then you probably have fond memories of the slide featuring Compensation tasks and Cancel events. It happens to be the very last slide in the symbol set section and is traditionally follows by a well deserved break. It also happens to be a very well implemented part of the Camunda engine.

This post is going to be all about how a process containing a transaction, cancel end event and compensation task are all implemented. The process I'm going to be describing is available on github to download and play with yourself. the process itself looks like this:

This process describes booking a holiday, as this is an example that requires a certain amount of failure, as with most failures it begins with a test. Specifically, a user task called "Personality Test". In this task you'll get to decide if you're you've got bad luck, bad finances or if in fact you're practically perfect in every way.

Bad Luck

If you choose bad luck a variable will be checked by the Book Hotel task will throw a BPMN error:

boolean bookingError = (Boolean) exec.getVariable("bookingHotelError");
        if(bookingError)
            throw new BpmnError("THIS_IS_NOT_GOOD");


The error will be caught by the error boundary event on the transaction sub-process. It's important to know that the compensation boundary event on the task itself is only triggered by the cancel end event. So in the case where the error is thrown the compensation event is not triggered. Instead the token will follow the flow of the error boundary event and end up at the "How did this happen task".

For this to work - no additional properties are required to the error boundary event, it is simply a "catch-all" for any errors that might occur.

Bad Finances

If you've decided on bad finances then the booking will pass by without a hitch but you won't be so luck when you try "Charge Credit Card". That service task will throw a different error:

        boolean bookingError = (Boolean) arg0.getVariable("chargeCardError");
        if(bookingError)
            throw new BpmnError("CHARGE_FAILURE");;
       


This case is different of course because the error boundary event is specifically waiting for a "CHARGE_FAILURE" error as defined in the properties. So when this happens the token is going to move straight to the cancel end event. This is where the engine works it's magic.

Without needing add any additional configuration yourself, the engine will take a look at the path the token has taken and work out if there are any compensation boundary events that need to be activated. In this case it will find that "Book Hotel" has a compensation boundary event leading to a compensation task.

A new token is generated for the sole purpose of running the "Cancel Hotel Reservation" task. Once all compensation tasks are complete the token is caught by the cancel boundary event on the transaction itself and continues along that flow to the "Cancellation Details" task.

Everything is Fine

Finally if you happen to choose the final option in which nothing bad happens - you've clearly missed the point of this example - but at least you have time to think about that while you enjoy your holiday.

For more information on these events take a look at our docs. More specifically if you want to read about Cancel and Compensation events, check this out.