Wie man Workflows mit Activiti als Open Source Process Engine leichtgewichtig in die Java-Welt bringt
Wie man Workflows mit Activiti als Open Source Process Engine leichtgewichtig in die Java-Welt bringt
Bisher ist uns als Entwickler das Thema BPM allzu oft unter zwei Gesichtspunkten begegnet: Einerseits gab es da das High-Level-PowerPoint-Heile-Welt-BPM auf Businessebene. Andererseits waren die BPM-Plattformen, mit denen wir uns dann am Ende des Tages abquälen, unflexibel und unhandlich. Aber BPM geht auch anders. Wir zeigen im Folgenden, wie sich mit der Open Source Process Engine Activiti einfache Workflows in Java EE 6 umsetzen lassen, mit großem Spaßfaktor bei der Entwicklung und dank BPMN 2.0 trotzdem „Business-aligned“.
In diesem Artikel wollen wir zeigen, wie man mit Activiti BPMN-2.0-Prozesse auf Basis von Java EE 6 umsetzen kann. Dazu haben wir uns als Anwendungsbeispiel die Bearbeitung einer Anfrage für ein Versicherungsangebot herausgesucht. Der Prozess ist in BPMN 2.0 modelliert und in Abbildung 1 zu sehen. Er beginnt mit dem Ereignis, dass ein Kunde ein Angebot für eine Versicherungspolice beantragt hat. Der nächste Schritt im Prozess ist dann eine Service Task, also eine automatisierte (wir denken: „Java“-)Tätigkeit, in der versucht wird, anhand der vom Kunden erhaltenen Daten ein Angebot zu erstellen. Dem Kunden wird dann die Aufgabe zugeordnet (UserTask), das Angebot zu akzeptieren oder abzulehnen. Mit dieser Aufgabe wird eine Frist verknüpft (siehe „angeheftetes Zeitereignis“). Läuft diese Frist ab, wird dem Kunden die Aufgabe entzogen, und der Prozess wird beendet. Falls der Kunde das Angebot annimmt, wird ein Vertrag erstellt und dem Kunden postalisch zugestellt.
Zur Modellierung des Prozesses lässt sich im Prinzip jedes BPMN-2.0-kompatible Tool verwenden. Das Activiti-Projekt stellt dazu ein eigenes Eclipse Plug-in bereit, den „Activiti Designer“. Verwenden wir diesen, entsteht eine Datei mit der Endung .bpmn20.xml. Diese XML-Datei ist das ausführbare Artefakt, das wir später an die Process Engine Activiti geben werden. Das Format dieser Datei wird durch den BPMN-2.0-Standard vorgegeben. Bevor wir den Prozess aber von Activiti ausführen lassen können, ist noch einiges an Implementierungsarbeit zu leisten. Wieso? Die Process Engine kann man sich wie eine Art „transaktionalen Zustandsautomaten“ vorstellen. Das heißt konkret: Wenn wir den Prozess ausführen, erlaubt Activiti es uns, einzelne Instanzen (= Durchläufe) von dem Prozess zu initiieren, die jeweils mit einer eindeutigen Identifikation (Process Instance ID) versehen werden. Der Zustand dieser Instanzen wird dann von Activiti für uns in einer Datenbank verwaltet. Wenn wir jetzt von Prozessimplementierung reden, dann reden wir von der Entwicklung dessen, was noch darum herum passieren muss, also zum Beispiel Eingabemasken bzw. Formulare, Services usw. Für diese Implementierung haben wir uns einen weiteren Standard ausgesucht, Java Enterprise Edition 6. Wir gehen an dieser Stelle also davon aus, dass der Leser zumindest mit Grundlagen von JSF und CDI vertraut ist.
Um zu zeigen, wie elegant das geht, steigen wir gleich ein. Wir hatten festgelegt, dass der Prozess durch das Ereignis gestartet wird, dass ein Kunde ein Angebot angefordert hat. Bei der Prozessimplementierung geht es jetzt darum, dieses logische „Ereignis“ zu konkretisieren. Eine Möglichkeit wäre es, die benötigten Daten mithilfe einer Eingabemaske vom Kunden abzufragen, zu sammeln und dann eine neue Instanz des Versicherungsprozesses zu starten, die diese Daten als Eingabe erhält. Wir entscheiden uns, diesen Ansatz mithilfe eines einfachen JSF-Formulars umzusetzen, das in Listing 1 gezeigt ist.
Listing 1
<h1>Antrag fuer die Kraftfahrtversicherung</h1>
<h:form>
<table>
<tr>
<td>Ihr Vorname:</td>
<td><h:inputText value="#{processVariables['vorname']}" /></td>
</tr>
<tr>
<td>Ihr Nachname:</td>
<td><h:inputText value="#{processVariables['nachname']}" /></td>
</tr>
<tr>
<td>Baujahr des Fahrzeuges:</td>
<td>
<h:inputText value="#{processVariables['kfzBaujahr']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText> (TT/MM/JJJJ)
</td>
</tr>
[...]
<tr>
<td />
<td><h:commandButton value="Fertig"
action="#{businessProcess.startProcessByKey
('versicherungsabschluss')}" />
</td>
</tr>
</table>
</h:form>
Offensichtlich haben wir die Möglichkeit, Prozessvariablen in einer vorgefertigten CDI Bean namens processVariables zu sammeln. Diese Bean wird für uns von der Activiti-CDI-Integration zur Verfügung gestellt. Die Bean ist vom Typ Map<String,Object> und erlaubt es uns, Objekte mit einem Namen zu versehen (Key der Map) und als Prozessvariablen an Activiti beim Starten einer neuen Prozessinstanz zu übergeben. Prozessvariablen werden dann von Activiti automatisch persistiert und für die Lebensdauer einer Prozessinstanz vorgehalten. Die im Formular gesammelten Daten (z. B. der Kaufpreis des Fahrzeugs) würden also auch den nachfolgenden Tasks im Prozess wieder zur Verfügung stehen und können so z. B. benutzt werden, um das Angebot zu generieren, selbst wenn dies erst Stunden später passieren sollte.
Aber wie kommen die Daten genau in die Prozessinstanz? Der Schlüssel dazu liegt in der letzten EL-Expression in Listing 1. Hier wird eine weitere zur Verfügung gestellte Bean benutzt, die businessProcess Bean. Diese erlaubt es uns, neue Prozessinstanzen zu starten und mit bestehenden Instanzen zu interagieren. In diesem Fall starten wir eine neue Instanz des Beispielprozesses, dem wir den Namen versicherungsabschluss gegeben haben. Dieser Aufruf startet eine neue Prozessinstanz und übergibt die Variablen, die wir in der processVariables Bean gesammelt haben, an diese Instanz. Hinter den Kulissen wird die processVariables Bean mit der aktuellen CDI Conversation bzw. dem aktuellen Request verknüpft. Wenn wir also ein mehrseitiges Formular hätten und auf der ersten Seite eine CDI Conversation starten würden, hätten wir die Möglichkeit, über mehrere Requests hinweg Variablen in der processVariables Bean zu sammeln, die wir dann auf der letzten Seite an die neu gestartete Instanz übergeben.
Verwendet man kein JSF, kann man natürlich das gleiche Resultat erzielen, wenn man in irgendeiner Weise die Prozessvariablen sammelt und dann den Prozess startet. Wenn man also z. B. ein UI mit HTML, JavaScript und REST bauen möchte...