jBPM4 real-life example: The Train Ticket Demo (part 2: coding the business process)

In the previous article I’ve showed how a business process born from a real-life problem can be modeled using the Signavio web-based jBPM editor. The end result consists of a BPMN model that is understandable by all parties involved, which is stored on the hard disk as  a JPDL file that is executable on the jBPM process engine.

Today, we’ll explore the second step in the BPM lifecycle, the actual development of the business process. When the business analyst delivers a first draft of the BPMN model, the developers team can get into action. After all, a business process is always business-specific, which means that no BPM product can ever cover every aspect of the business out-of-the-box (altough some do promise this).

Business-specific logic

The main strength of jBPM is its embeddability into any Java environment. Whereas many BPM products are big black-box servers, jBPM is at its core just a simple Java library that just fits into any architecture. This means that writing your business specific logic boils down to writing regular Java, no magic involved. All the rules that apply to writing normal Java applications are applicable to writing logic for your business process, which means that you don’t need to learn yet another language to actually go forward.

Since jBPM is Just A Java Library (TM), this also means you can just write regular (J)Unit test (or TestNG, or whatever gets you going) to actually test the business process logic. Once again, nothing in my sleeves, no magic involved. The business process described in the previous article is graphically shown below:

The BPMN model generated by the Signavio editor can be imported straight into the Eclipse based Graphical Process Designer (GPD) – remember the end of the screencast of the previous article. The GPD allows you to implement the business logic graphically or in the JPDL source format. For me personally, the JPDL source editing has always been my preferred choice (once you know your way around, it’s extremely fast), but you can pick whatever you like.

One of the goals of the JPDL format, which is an XML language by the way, is to have a lanuage that is as concise, readable  and as rich as possible. Take a look at the JPDL version of the process above, now enhanced with Java logic by the dev team:

<process name="ticketProcess" xmlns="http://jbpm.org/4.0/jpdl">

<start name="start">
  <transition to="Calculate quote"/>
</start>

<custom name="Calculate quote" class="org.jbpm.trainticketdemo.action.CalculateQuoteAction">
  <transition to="Check customer credit"/>
</custom>

<custom name="Send price quote to customer" class="org.jbpm.trainticketdemo.action.SendQuoteToCustomerAction">
  <transition to="Accept quote"/>
</custom>

<custom name="Send reject message" class="org.jbpm.trainticketdemo.action.SendQuoteToCustomerAction">
  <transition to="cancel"/>
</custom>

<decision name="Check customer credit">
  <handler class="org.jbpm.trainticketdemo.decision.CheckCustomerDecision"  />
  <transition name="credit OK" to="Send price quote to customer"/>
  <transition name="credit NOK" to="Send reject message"/>
</decision>

<task assignee="#{cellPhoneNr}" name="Accept quote">
  <transition to="charge customer"/>
  <transition name="timeout" to="cancel">
    <timer duedate="1 day"/>
  </transition>
</task>

<custom name="charge customer" class="org.jbpm.trainticketdemo.action.ChargeCustomerAction">
  <transition to="end"/>
</custom>

<end-cancel name="cancel"/>
<end name="end"/>
</process>

Notice how easy to read this process format is, and compare it with other formats (I’m not saying which, but it tends to end with ‘PEL’).

You can easily see how easy it is to attach business-specific logic to the business process:

<custom name="Calculate quote" class="org.jbpm.trainticketdemo.action.CalculateQuoteAction">
  <transition to="Check customer credit"/>
</custom>

The CalculateQuoteAction class is a just regular POJO that implements an interface defined by the jBPM API:

public class CalculateQuoteAction implements ActivityBehaviour {

  private QuoteService quoteService;

  public CalculateQuoteAction() {
    this.quoteService = EjbUtil.getQuoteService();
  }

  public void execute(ActivityExecution execution) {

   String from = (String) execution.getVariable("from");
   String to = (String) execution.getVariable("to");
   String cellphoneNr = (String) execution.getVariable("cellNr");

   Quote quote = quoteService.calculateQuote(from, to, cellphoneNr);

   execution.setVariable("quote", quote);
  }

}

The ActivityBehaviour interface defines one method ‘execute‘ which will be called by the process engine once the business process engine arrives in this activity.

Remember, the real calculation is done on a system already implemented by the train operator (see process diagram), so the logic is very easy here:

  • retrieve the input variables (from, to, cellphone number) from the process instance (the variables are stored in the process instance when the process is started, see the unit test later)
  • calculate a price quote by calling a remote EJB service (wrapped in the EjbUtil class). Here it is an EJB service,  but it can really be virtually anything you can imagine doing in Java code.
  • store the resulting price quote as a process variable in the process instance.

The parallel gateway (decision) which will determine which path to follow in the process, depending on the customer credit, is also very simple:

public class CheckCustomerDecision implements DecisionHandler {

  private static final String OK_DECISION = "credit OK";
  private static final String NOT_OK_DECISION = "credit NOK";

  public String decide(OpenExecution execution) {
    Quote quote = (Quote) execution.getVariable(ProcessVariable.QUOTE);
    User user = getUser(quote);

    if (user != null && hasEnoughCredit(user, quote)) {
      return OK_DECISION;
    }

    return NOT_OK_DECISION;

  }
...

And I can go on quite a long time by pasting these snippets. If you want to see all the handlers and activities, just take a look at out svn repo.

Typically, each business-specific class will not have many lines of code. If this is not the case, then you probably can remodel your process to something less complex and better understandable.  Every node in the diagram should have one responsibility, ideally.  In practice, you’ll notice that after a while you’ll be able to start reusing these little POJO classes.

Also note how SOA-ready these little classes are: in a SOA environment the business specific code will typically be nothing more than just calls to services (which is the case with the examples above). jBPM will help you greatly here, since you can write Java code (and connect to any service reachable by Java technology) and you don’t have to wrap services in a webservice for example.

The last construct I want to explain is the task:

<task <strong>assignee="#{cellPhoneNr}"</strong> name="Accept quote">
  <transition to="charge customer"/>
  <transition name="timeout" to="cancel">
    <timer duedate="1 day"/>
  </transition>
</task>

A lot is going on here:

  • The task is assigned to the cellphone number which is one of the inputs of the process. Notice the use of expressions (#{…}) here, which make the process XML very readable.
  • The process will wait for a task completion event forever, if it wasn’t for the timer construct on line 4.  After a day, the timer fires and the ‘timeout’ transition will be taken (which will go to a cancel-end). This way, no process instances will be running longer than a day.

The Unit Test

Like I said in the previous paragraph, the huge benefit of jBPM is that everything is plain Java. So writing a unit test is very recognizable:

public void testTrainTicketProcessWithEnoughCredit() {

  // First we deploy the latest version of the train ticket process
  NewDeployment deployment = repositoryService.createDeployment();
  deployment.addResourceFromClasspath("../process.jpdl.xml");
  deployment.deploy();

  // Start a new Process instance
  Map<String, String> vars = new HashMap<String, String>();
  vars.put(ProcessVariable.FROM, "Brussels");
  vars.put(ProcessVariable.TO, "Antwerp");
  vars.put(ProcessVariable.CELLPHONE_NUMBER, testUser.getCellphoneNr());

  ProcessInstance pi = executionService.startProcessInstanceByKey("ticketProcess", vars);

  // Since I have enough money, the 'Accept Quote' task should be assigned to the test user's cellphone nr
  List<Task> tasks = taskService.findPersonalTasks(testUser.getCellphoneNr());
  assertTrue("Nr of tasks for cellphone nr = " + tasks.size(), tasks.size() == 1);

  // or we can use the new Query API
  Task task = taskService.createTaskQuery()
      .assignee(testUser.getCellphoneNr())
      .uniqueResult();
  assertEquals(tasks.get(0).getName(), task.getName());

  // After task completion, the process is finished
  taskService.completeTask(task.getId());
  assertProcessInstanceEnded(pi);

  // We always clean up after ourselves
  repositoryService.deleteDeploymentCascade(deployment.getId());

}

See the svn repo for the full source.

If you haven’t seen the new jBPM API yet, do take a closer look at the unit test code and the comment lines I added. As you can see, the process is kickstarted by calling the executionService, providing it a process definition name and a collection of variables (from, to, and the cellphone nr).

To actually run the unit test, you’ll need to configure the jBPM process engine. This configuration has ondergone a serious facelift with respect to jBPM3 (where the config could get messy):

<jbpm-configuration>
<import resource="jbpm.default.cfg.xml"/>
<import resource="jbpm.tx.hibernate.cfg.xml"/>
<import resource="jbpm.jpdl.cfg.xml"/>
<import resource="jbpm.identity.cfg.xml"/>
<import resource="jbpm.businesscalendar.cfg.xml"/>
<import resource="jbpm.jobexecutor.cfg.xml"/>
</jbpm-configuration>

This config gives us an environment with Hibernate, transactions and a job executor. Couldn’t be easier, right?

The Unit Test in Action

Of course, as I promised I will prove all the articles in this series with real source code, and screencasts. Click on the picture below to view the next screencast. In this movie, the unit test of above is shown to actually execute within Eclipse (Maven would also work).

What’s next

We’ve already tackled the process modeling and the first go at adding custom business logic. In the next article, we’ll take a closer look at the jBPM web-console and how it can be used for prototyping business processes before actually building the real application.

Stay tuned!

11 Comments

  1. George September 11, 2009

    Great demo and explanations! Many thanks for sharing this. I was wondering why you chose a “custom” activity over an automatic activity — such as “java”. Is there a fundamental difference?

  2. Joram Barrez September 12, 2009

    @George: Thanks for the encouragement! Keep on eye on my RSS feed, because the next articles are going to be really awesome ;-)

    There is indeed one fundamental difference between the ‘custom’ and ‘java activity’. The ‘custom’ activity implements a jBPM interface, BUT is has also access to the execution that way. The Java activity doesn’t have access to the execution, but you can call just any method on the class you define. Since I need the execution to retrieve and store the process variables (see CalculateQuoteAction), I need to use a custom activity.

    An additional advantage of the custom activity is that you can control the execution. If you want the activity to behave as a wait state, then you can do this very easily.

    So to conclude: a Java activity is for a true automatic piece of logic, a custom activity is for anything else (only limited by your imagination). In practice, you’ll probably use the custom activity more than the Java activity.

  3. Andrey September 21, 2009

    Is there some installation howto?

    I’ve downloaded from http://anonsvn.jboss.org/repos/jbpm/projects/demos/trainticket-demo/jbpm-on-rails/trunk/process-logic/ and tried to import pom.xml into my eclipse … the build failed, after fixing some dependencies the hibernate failed, after fixing hibernate config, something else failed … “ould not obtain connection to any of these urls: localhost:1099″ …

    Are there some tricks I have to do? Straightforward method (download -> import -> run) doesn’t work for me.

    Thank you,
    Andrey

  4. Joram September 30, 2009

    @Andrey: To run the unit test, the easiest is to get the jboss server running. Download the all-in-one-zip, and start the server.

    The unit test should be running now (I should rewrite the test using embedded JBoss, I know)

  5. Pat October 19, 2009

    Hello, I would like to import this demo project into Eclipse to study the source, and run it through debug, etc. Please detail exactly how to import into Eclipse.

  6. Joram Barrez October 20, 2009

    @Pat: You should have Eclipse with the m2Eclipse plugin. If you have that plugin installed, it should just be a matter of doing File -> import -> maven project.

  7. Pat October 20, 2009

    Sorry, but I’m still missing something (probably something very obvious), but exactly WHERE is the .zip project file that I download, in order to Import it into Eclipse as a maven project, so that I can view/debug TrainTicketProcessUnitTestDemo.java. , process.jpdl.xml, CalculateQuoteAction.java, etc.

  8. Joram Barrez October 21, 2009

    @Pat: You can find the source on our svn: just checkout the trunk directories from https://anonsvn.jboss.org/repos/jbpm/projects/demos/trainticket-demo/

  9. Pat October 21, 2009

    Thank you for your quick responses and patience with my basic newbie questions.

    The piece I was missing was that I needed to download/execute Subversion from Collabnet in order to export your entire project out of your svn trunk. I’ve done that, and now I have what I need.

  10. Tom Nguyen December 8, 2009

    Thanks for the sample, How can we customize task class in jbpm?

  11. Matthias February 1, 2011

    Great, helped a lot! Brings up development to speed as it is totally focussed and straightforward.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>