Keep informed?
Subscribe for our newsletter now!

How do you make a Cappuccino in a single Transaction?

In my last blogpost I presented some experiments on how to model Write-off, Retry, Compensation and Two Phase Commit using BPMN 2.0. As an example I used a process which explains how to make a cappuccino, including both coffee and milk. I then discussed how we can handle the fact that there is no milk. Some people, including my wife and our product owner, have asked me questions about the two phase commit (2PC) example. One thing you learn as a software engineer is, that it is very important to keep your wife and your product owner happy. So here is an attempt at explaining how to make cappuccino in a transactional fashion.

First, let’s revisit the example:

The “make coffee” and “add milk” tasks are grouped using a transactional subprocess:

I wrote (rather clumsily):

“If a failure occurs, the customer is still unhappy but we did not waste any resources (we didn’t throw any coffee away). Implementing this might require some more expensive and more complex infrastructure.”

The question people asked is:

“But you first make coffee and then you add the milk. And it is only when adding the milk, when you detect that you do not have any milk left! So how is it possible that there is no coffee being made, if there is not enough milk even it the “make coffee” activity is apparently being executed before the “add milk” activity?”.

I think the question is perfectly understandable if you are unfamiliar with the concept of transactions. But surprisingly it is possible to model the process as shown above and implement it in a way that there is no coffee wasted if there is no milk. Sounds like magic? Perhaps. What we need is this magical thing called a transaction.

(If you are familiar with transactions: please also read the disclaimers at the end of this post 🙂 )

What is a transaction?

A transaction allows grouping multiple operations in a way such that they perform as if they were a single operation. The technical term is that they execute atomically, meaning as an atomic entity. Practically speaking, this means that they succeed or fail collectively (together). If one operation fails, all operations fail, leaving behind no unwanted traces. This also means that a transaction always executes completely or not at all. There is no quitting in the middle.

(There are additional properties of a transaction like the fact that it executes in isolation from other concurrent transactions and that it produces durable, consistent results.)

What does a transaction program look like in Java?

If you are a Java programmer like me, your transactions typically look a lot like this (yes I know, I left out some if-s and else-s):

try {
   transaction.begin()
   // do something
   // do something else
  transaction.commit();
}catch(Exception e) {
  transaction.rollback();
}

So a transaction program is basically an “ordinary” program bracketed using begin() and commit()/rollback().

The begin()-method starts a transaction. This means that everything that follows must be executed using transactional semantics (“all or nothing”). The commit()-method ends a transaction. This is our program saying “I am done, everything went well from my side, we can finish this now”. If something goes wrong, the program calls the rollback()-method. This is the program’s way of saying “Something went wrong, I want to take everything back I said since begin(). Please do as if nothing happened”.

What would the BPMN example look like as a Java Program?

It would probably look a lot like this:

Coffee c = null;
try {
  transaction.begin()
  c = coffeeUnit.makeCoffee();
  milkUnit.addMilk(c); // throws no-milk-exception
  transaction.commit();
}catch(Exception e) { // catches no-milk-exception
  transaction.rollback();
  return;  // do not continue
}
me.drinkCoffee(c);

So this is the program’s view of things. But in order to actually execute this, someone must implement begin(), commit(), makeCoffee(), addMilk() and so on.

Programs, Managers and Resources

(When reading this, please keep the Disclamers in mind 🙂 )

In order to provide transactional semantics, three parties have to collaborate:

  • The transaction program is responsible for demarcating transactions using begin() and commit()/rollback() and for calling operations on transactional resources, like makeCoffee() and addMilk(). In our BPMN / coffee example, the equivalent of the transaction program is the BPMN process model.
  • A transactional resource provides a set of operations to be called by a program. If such an operation is called, it registers with the transaction manager and enlists in the current transaction to be notified about commit() rollback() later. In our BPMN / coffee example, the transactional resources are the coffee maker and the milk dispenser.
  • A transaction manager provides an implementation of the begin() / commit() / rollback() methods and a registry for transactional resource enlistment. At begin(), a new transaction is started and identified with a unique ID. Each transactional resource that is used by the program is then enlisted with this transaction. When a commit() or rollback() occurs, the transaction manager is responsible for coordinating between the resources (make sure that either all resources commit() or all resources roll-back).

I tried to visualize this:

Let’s briefly walk through the steps:

1.) The process engine starts the transaction subprocess and calls begin() on the transaction manager.

2.) The process engine executes the first activity and calls makeCoffee() on the coffee maker resource.

3.) The coffee maker enlists with the transaction manager. This way, the transaction manager is aware that the coffee maker participates in the transaction and must be notified about commit() and rollback(). At this point the coffee maker (implementation of the coffeeMaker.makeCoffee() operation) will check whether there is enough coffee left. If true, it will reserve (lock!) that amount of coffee for the current transaction (no concurrent transaction will be able to use up that coffee). If no coffee is left, it will throw an exception which is communicated back to the process engine.

4.) The process engine executes the second activity and calls addMilk() on the milk dispenser resource.

5.) The milk dispenser enlists with the transaction manager. At this point the milk dispenser will check whether there is enough milk left. If true it will reserve (lock!) that amount of milk. If there is no milk left, it will throw an exception which is communicated back to the process engine.

6.a) If no exception is thrown, the process engine calls commit() on the transaction manager. The transaction manager notices that there are two resources enlisted with the transaction, the milk dispenser and the coffee maker. **

7.a) The transaction manager runs the two phase commit (2PC) protocol. The 2PC protocol makes sure that either both resources commit or both roll back. It will first issue a prepare() to both resources, asking them to prepare for commit. If both resources determine that they can commit, they communicate this to the transaction manager who will call commit() on both resources. (If after this one of the resources or the transaction manager fails (goes down), the transaction manager will run recovery, when everything is back up again.) In the implementation of the commit() method, the coffee maker will notice that the makeCoffee() method has been called before (step 2). Only now, it will actually make and flush the coffee. The same is true for the milk dispenser.

6.b) If an exception is thrown, the process engine calls rollback() on the transaction manager.

7.b) The transaction manager calls rollback() on on both the coffee maker and the milk dispenser. In the implementation of the rollback() method, both resources will free (unlock!) the reserved amount of coffee and milk.

The important parts are highlighted above.

(It should be noted that BPMN 2.0 also supports compensation. A transaction subprocess can use both compensation and transactions. For example you can compensate wait states or other activities that have already committed using compensation. In that case, if a transaction is aborted (see step 6.b above),  the transaction is rolled back using the transaction manager AND compensation is triggered. When the last compensation handler completes, the subproces is left using the sequence flow running out of the cancel boundary event.)

Conclusion

We have seen how to implement the BPMN process, leveraging the principle of transactions. If you somehow got lost between all the resources, managers, commits and rollbacks, here is a graphical summary:

 

The intuition behind this is that when called inside a transaction, the “make coffee” operation will not actually make coffee, just reserve (lock) the necessary resources and remember to make it if the whole transaction completes (commits). The same is true for the “add milk” activity.

This also means that if you provide implementations of transactional resources (if you implement a “coffee maker” or “milk dispenser”), you have to break up the implementation of your operations into a “check and lock” part (“do we have enough milk? If true reserve it.”) and a “commit” part (“now actually flush the milk to the cup.”).

One thing that makes transactions interesting is that once you do break up your operations like this, it is transparent to the user. Transactions allow modeling programs in the “traditional” way, using sequence, branches, exceptions etc., but when these programs are executed, they behave quite differently (atomically, isolated, producing durable and consistent results). The fact that this is transparent is probably what threw my wife and our product owner off track here.

So is it magic? No! Just great engineering!

————–

** If you are familiar with the concept of transactions you are probably asking yourself now: “But what happens if there is a waitstate in the transaction subprocess?” or “What happens if there is concurrency? Aren’t most transaction protocols somewhat thread-bound?” or “How do you handle nesting?”, etc… – I am asking myself these exact questions. I have some answers also, I hope to present them at some point here.

————–

Disclaimer 1: The intent of this article is not to be correct when it comes to different transaction models. The concept of transactions is much richer and deeper than what is presented here! There are Transactions with ACID properties, there is two phase commit, there are extended transaction models (see for instance the WS-T specifications), there are nested transactions in all imaginable variants, short, long, red, blue and green. Etc..

Disclaimer 2: If I say “Two Phase Commit (2PC)”  in this article, I do not *actually* mean Two Phase Commit. I use it as a synonym for all other consensus-based coordination protocols that allow determining the outcome of a distributed transaction based on participant-consensus. There are also many shapes and colors here.

Already read?

Scientific performance benchmark of open source BPMN engines

Why BPMN is not enough

Decision Model and Notation (DMN) – the new Business Rules Standard. An introduction by example.

New Whitepaper: The Zero-Code BPM Myth

2 Responses

Leave a reply