Activiti 5.9 introduces BPMN Compensation and Transactions
Please note that this blog post is outdated, since we forked Activiti in March 2013 and started a new Open Source BPM project called camunda BPM (www.camunda.org). You will find this topic and loads of more best practices, blueprints etc. there, so you should just have a look 🙂
Compensation is a means for “undoing” the effects of an action. For example, say you charge the amount of 500€ to a credit card. If you later detect that this was an error, you may want to undo this. One way of doing that is crediting the same account 500€.
Now wait, isn’t that what transactions are for? Can I not just rollback the transaction in which I charged the 500€ and I am done? – Well yes, but there are two problems:
- The service you are using might not support transactions.
- You might notice that it was wrong to charge the 500€ too late, at a time where the transaction is already committed.
In both cases we cannot simply “go back in time” and do as if nothing happened. On the contrary, we need to make something else happen, in order to compensate the effects of an action we erroneously committed.
(As we know from real life experience, this is not always that simple and therefore, in general, compensation is not always a strict “undo” action. Consider the case where you send a letter to a customer and then later notice that you need to compensate the sending of the letter. At that point, the customer might have already read the letter. You cannot simply “undo” that fact. What you might do in that situation is send a second letter, asking the customer to forget about the first one.)
Compensation in BPMN 2.0
In BPMN, the concept of compensation is introduced, using compensation events and compensation handlers. For example, the following excerpt of a process shows how we can declare that the “book hotel” activity can be compensated using the “cancel hotel reservation” activity. If a task has multiple instance characteristics (like the “book hotel” task in this example), the compensation handler is invoked for each instance that completed successfully.
When compensating a subprocess, the execution used for executing the compensation handlers has access to the local process variables of the subprocess in the state they were in when the subprocess completed execution. To achieve this, a snapshot of the process variables associated with the scope execution (execution created for executing the subprocess) is taken. Form this, a couple of implications follow:
- The compensation handler does not have access to variables added to concurrent executions created inside the subprocess scope.
- Process variables associated with executions higher up in the hierarchy, (for instance process variables associated with the process instance execution are not contained in the snapshot: the compensation handler has access to these process variables in the state they are in when compensation is thrown.
- A variable snapshot is only taken for subprocesses, not for other activities.
Compensation in activiti 5.9
Activiti supports compensation boundary events and intermediate throwing compensation events.
The following limitations apply:
waitForCompletion="false"is currently unsupported. When compensation is triggered using the intermediate throwing compensation event, the event is only left, after compensation completed successfully.
- Compensation itself is currently performed by concurrent executions. The concurrent executions are started in reverse order in which the compensated activities completed. Future versions of activity might include an option to perform compensation sequentially.
- Compensation is not propagated to sub process instances spawned by call activities.
Transaction subprocesses in BPMN 2.0
A transaction subprocess can be used to group multiple activities to a logical unit of work with a consistent outcome. Since BPMN transactions are long running transactions, they make use of compensation for ensuring consistency in case a transaction fails.
A transaction can have three different outcomes:
- A transaction is successful, if it is neither cancelled nor terminated by a hazard. If a transaction subprocess is successful, it is left using it’s outgoing sequenceflow(s). A successful transaction might be compensated if a compensation event is thrown later in the process.
Note: just as “ordinary” subprocesses, a transaction may be compensated after successful completion using an intermediary throwing compensation event.
- A transaction is cancelled, if an execution reaches the cancel end event. In that case, all executions are terminated and removed. A single remaining execution is then set to the cancel boundary event, which triggers compensation. After compensation is completed, the transaction subprocess is left using the outgoing sequence flow(s) of the cancel boundary event.
- A transaction is ended by a hazard, if an error event is thrown, that is not caught within the scope of the transaction subprocess. (This also applies if the error is caught on the boundary of the transaction subprocess.) In this case, compensation is not performed.
The following diagram illustrates the three different outcomes of a transaction:
Relation to ACID transactions: it is important not to confuse the bpmn transaction subprocess with technical (ACID) transactions. The bpmn transaction subprocess is not a way to scope technical transactions. A bpmn transaction is different from a technical transaction in the following ways:
- While an ACID transaction is typically short lived, a bpmn transaction may take hours, days or even months to complete. (Consider the case where one of the activities grouped by a transaction is a usertask, typically people have longer response times than applications. Or, in another situation, a bpmn transaction might wait for some business event to occur, like the fact that a particular order has been fulfilled.) Such operations usually take considerably longer to complete than updating a record in a database, or storing a message using a transactional queue.
- Because it is impossible to scope a technical transaction to the duration of a business activity, a bpmn transaction typically spans multiple ACID transactions.
- Since a bpmn transaction spans multiple ACID transactions, we loose ACID properties. For example, consider the example given above. Let’s assume the “book hotel” and the “charge credit card” operations are performed in separate ACID transactions. Let’s also assume that the “book hotel” activity is successful. Now we have an intermediary inconsistent state, because we have performed an hotel booking but have not yet charged the credit card. Now, in an ACID transaction, we would also perform different operations sequentially and thus also have an intermediary inconsistent state. What is different here, is that the inconsistent state is visible outside of the scope of the transaction. For example, if the reservations are made using an external booking service, other parties using the same booking service might already see that the hotel is booked. This means, that when implementing business transactions, we completely loose the isolation property (Granted: we usually also relax isolation when working with ACID transactions to allow for higher levels of concurrency, but there we have fine grained control and intermediary inconsistencies are only present for very short periods of times).
- A bpmn business transaction can also not be rolled back in the traditional sense. Since it spans multiple ACID transactions, some of these ACID transactions might already be committed at the time the bpmn transaction is cancelled. At this point, they cannot be rolled back anymore.
Since bpmn transactions are long-running by nature, the lack of isolation and a rollback mechanism need to be dealt with differently. In practice, there is usually no better solution than to deal with these problems in a domain specific way:
- The rollback is performed using compensation. If a cancel event is thrown in the scope of a transaction, the effects of all activities that executed successfully and have a compensation handler are compensated.
- The lack of isolation is also often dealt with using domain specific solutions. For instance, in the example above, an hotel room might appear to be booked to a second customer, before we have actually made sure that the first customer can pay for it. Since this might be undesirable from a business perspective, a booking service might choose to allow for a certain amount of overbooking.
- In addition, since the transaction can be aborted in case of a hazard, the booking service has to deal with the situation where a hotel room is booked but payment is never attempted (since the transaction was aborted). In that case the booking service might choose a strategy where a hotel room is reserved for a maximum period of time and if payment is not received until then, the booking is cancelled.
To sum it up: while ACID transactions offer a generic solution to such problems (rollback, isolation levels and heuristic outcomes), we need to find domain specific solutions to these problems when implementing business transactions.
Transaction Subprocesses in Activiti 5.9
The BPMN specification requires that the process engine reacts to events issued by the underlying transaction protocol and for instance that a transaction is cancelled, if a cancel event occurs in the underlying protocol. As an embeddable engine, activiti does currently not support this.
A bpmn transaction guarantees consistency in the sense that either all activities compete successfully, or if some activity cannot be performed, the effects of all other successful activities are compensated. So either way we end up in a consistent state. However, it is important to recognize that in activiti, the consistency model for bpmn transactions is superposed on top of the consistency model for process execution. Activiti executes processes in a transactional way. Concurrency is addressed using optimistic locking. In activiti, bpmn error, cancel and compensation events are built on top of the same acid transactions and optimistic locking. For example, a cancel end event can only trigger compensation if it is actually reached. It is not reached if some undeclared exception is thrown by a service task before. Or, the effects of a compensation handler can not be committed if some other participant in the underlying ACID transaction sets the transaction to the state rollback-only. Or, when two concurrent executions reach a cancel end event, compensation might be triggered twice and fail with an optimistic locking exception. All of this is to say that when implementing bpmn transactions in activiti, the same set of rules apply as when implementing “ordinary” processes and subprocesses. So to effectively guarantee consistency, it is important to implement processes in a way that does take the optimistic, transactional execution model into consideration.
The following is a larger example of a transction subprocess which can be executed by activiti: