Will man im Rahmen des Business Process Management (BPM) die betrieblichen Prozesse erfassen, automatisieren und optimieren, macht es im Allgemeinen schon viel Sinn auf die Mächtigkeit einer ausgereiften Open-Source-Workflow-Engine - wie Camunda - zurückzugreifen. Diese setzt - genauso wie andere Workflow-Engines - auf den Industrie-Standard BPMN, der es erlaubt einen Prozess aus grafischen Elementen mit klar definierter Semantik aufzubauen. Nach Anreicherung mit Engine-spezifischen Meta-Daten, kann der Prozess dann innerhalb der Engine ausgeführt werden.

Diagram first

Der klassische - und auch zu präferierende - Ansatz ist es, einen Prozess zunächst grafisch mit einem entsprechenden Werkzeug zu modellieren. Als Beispiel soll ein vereinfachter Pizza-Bestell-Prozess (OrderPizzaProcess1) dienen. Der Quellcode ist in https://github.com/PixelGmbH/camunda-ohne-bpmn hinterlegt.

OrderPizzaProcess1
OrderPizzaProcess1

  1. Bestellung der Pizza
  2. Warten auf die Pizza-Lieferung

    • wenn diese nach 45min nicht eintrifft, wird noch mal beim Lieferdienst nachgefragt; die Prozess-Instanz bleibt weiterhin aktiv
    • wenn diese nach einer Stunde nicht eintrifft, wird die Prozess-Instanz abgebrochen
  3. Pizza essen

An jeden Task wurde PizzaExecutionListener als sogenannter ExecutionListener angehängt, der einfach den Namen des gerade aktiven Tasks über den Logger ausgibt:

ExecutionListener
ExecutionListener

public class PizzaExecutionListener implements ExecutionListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(PizzaExecutionListener.class);

    @Override
    public void notify(DelegateExecution delegateExecution) throws Exception {
        LOGGER.info("{}", delegateExecution.getCurrentActivityName());
    }
}

Über Unit-Tests können die einzelnen Pfade durch den Prozess abgetestet werden. Beispielsweise wird in folgendem Test aus OrderPizzaProcess1Test nach dem Prozess-Start die Zeit 50m in die Zukunft gesetzt und dann das Eintreffen der Pizza bekannt gegeben. Ein Asserter prüft schließlich, ob bestimmte Tasks durchlaufen wurden oder nicht.

@Autowired
public RuntimeService runtimeService;

@Test
public void complain_pizza_order() throws Exception {
    // given
    String processDefinitionKey = "OrderPizzaProcess1";

    // when
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey);
    assertThat(processInstance).isStarted();

    LocalDateTime time = LocalDateTime.ofInstant(ClockUtil.getCurrentTime().toInstant(), ZoneId.systemDefault());
    time = time.plusMinutes(50);
    ClockUtil.setCurrentTime(Date.from(time.atZone(ZoneId.systemDefault()).toInstant()));

    Thread.sleep(2 * 1000);
    EventSubscription subscription = runtimeService.createEventSubscriptionQuery()
            .processInstanceId(processInstance.getId()).activityId("WaitForPizza").eventType("message").singleResult();
    runtimeService.messageEventReceived("PizzaReceivedMessage", subscription.getExecutionId());

    // then
    assertThat(processInstance).isEnded().hasPassed("OrderPizza", "WaitForPizza", "ComplainToDeliveryService", "EatPizza")
            .hasNotPassed("CancelOrder");
}

Dieser Ansatz hat natürlich den Vorteil, dass initial ein BPMN-Diagramm vorliegt, das auch von Angehörigen des Fachbereichs verstanden werden kann. Gestandene Java-Entwickler haben dagegen oft Vorbehalte gegen die Verwendung eines speziellen Modellierungs-Werkzeugs, da das ja auch einen Bruch mit der Lieblings-IDE bedeutet. Darüber hinaus muss man sich auch noch mit dem Layout des Diagramms auseinandersetzen.

Code first

Um die Prozess-Modellierung entwickler-freundlicher zu gestalten, bietet Camunda eine Alternative an: Über die BPMN Model API kann rein programmatisch ein Prozess OrderPizzaProcess2 definiert werden, der nach dem Hochfahren der Engine, dieser zugänglich gemacht wird. Hier der relevante Auszug aus der Spring-Konfiguration:

private BpmnModelInstance model() {
  return Bpmn.createProcess()
              .id("OrderPizzaProcess2")
              .executable()
          .startEvent()
          .manualTask("OrderPizza")
              .name("Order Pizza")
              .camundaExecutionListenerClass("start", PizzaExecutionListener.class)
          .receiveTask("WaitForPizza")
              .name("Wait for Pizza")
              .camundaExecutionListenerClass("start", PizzaExecutionListener.class)
              .message("PizzaReceivedMessage")
              .boundaryEvent()
                      .cancelActivity(false)
                      .timerWithDuration("PT45M")
                      .name("45m")
                  .manualTask("ComplainToDeliveryService")
                      .name("Complain to Delivery Service")
                      .camundaExecutionListenerClass("start", PizzaExecutionListener.class)
                  .endEvent()
                  .moveToActivity("WaitForPizza")
              .boundaryEvent()
                      .timerWithDuration("PT1H")
                      .name("1h")
                  .manualTask("CancelOrder")
                      .name("Cancel Order")
                      .camundaExecutionListenerClass("start", PizzaExecutionListener.class)
                  .endEvent()
                  .moveToActivity("WaitForPizza")
          .manualTask("EatPizza")
              .name("Eat Pizza")
              .camundaExecutionListenerClass("start", PizzaExecutionListener.class)
          .endEvent()
          .done();
}

Der Fluent-Style erlaubt eine kompakte Definition, die sich bei entsprechender Formatierung auch recht übersichtlich gestalten lässt.

Die Tests in OrderPizzaProcess2Test zeigen, dass sich dieser Prozess analog zu OrderPizzaProcess1 verhält. Über den Aufruf

Bpmn.writeModelToFile(new File("target/pizza2.bpmn"), modelInstance);

lässt sich sogar nachträglich eine BPMN-Datei erzeugen, wobei das Diagramm (zurzeit) leider nicht ganz korrekt angeordnet wird. Eine Präsentation gegenüber dem Fachbereich erfordert also etwas manuellen Anpassungsaufwand:

OrderPizzaProcess1
OrderPizzaProcess1

Dieser Ansatz setzt bei all dem weiterhin voraus, dass man sich mit den Konzepten der BPMN beschäftigt. Da es sich dabei - wie schon gesagt - um einen Industrie-Standard handelt, ist diese Investition auf jeden Fall zukunftssicher.