TL;DR;

Service Tasks sind ein einfacher Weg um aus einem Camunda Prozess heraus Code auszuführen. Für die Implementierung von Service Tasks gibt es verschiedene Möglichkeiten.

Bei der Implementierung als „JavaClass“ muss eine Klasse, die das Interface JavaDelegate implementiert, angegeben werden. Hier kann dann in der Methode execute() beliebiger Code ausgeführt werden.

Wollt ihr nur eine einzelne Zeile Code ausführen, bietet sich die Implementierung als Expression an. Dabei könnt ihr euren Code direkt im BPMN Model hinterlegen z.B.${calculationService.doCalculation()}.

Die Delegate Expression kombiniert beide Ansätze. Hier muss die Expression eine Instanz zurückgeben, welche JavaDelegate implementiert. Beispielsweiße eine Bean aus dem ApplicationContext: ${doCalculationDelegate}.

 

Nachdem ich im letzen Artikel erklärt habe wie wir unseren ersten Prozesse in die Camunda Engine deployen, wird es jetzt Zeit dass der Prozess auch etwas sinnvolles tut.

Um in einem BPMN Prozess etwas „zu tun“, haben wir verschiedene Möglichkeiten. In diesem Beispiel wollen wir uns mit der einfachsten und wohl am häufigsten verwendeten Weise, dem Service Task, beschäftigen.

Kurz gesagt ermöglich ein Service Task das Ausführen von Java Code im Laufe des Prozesses. In diesem Java Code können wir dann alles, von der einfachen Berechnung, bis hin zu langläufigen und komplexen Serviceaufrufen ausführen.

 

Einen Service Task in den Prozess einbauen

Dafür fügen wir zuerst einmal einen Task in unseren Prozess ein. Diesem geben wir eine sinnvolle Bezeichnung und eine ID. Die Bezeichnung solle so gewählt werden, dass sie beschreiben was der Task tut z.B. „Preis berechnen“, „Vertag überprüfen“.

Wer mehr über eine Sinnvolle Namensgebung lesen möchte, kann sich mal den folgenden Artikel anschauen:

https://camunda.com/bpmn/examples/#bpmn-modeling-styles-naming-conventions

Normale Tasks werden von der Process Engine bei der Ausführung einfach übersprungen. Damit die Engine etwas mit dem Task anfangen kann, müssen wir auswählen um was für eine Art von Task es sich handelt. Dafür klicken wir auf den kleinen Schraubenschlüssel neben dem Task und wählen in unserem Fall „Service Task“ aus.

Sobald wir den Typ ausgewählt haben, erhält unser Task im Diagram ein kleines Zahnrad, welches ihn als Service Task markiert.

Jetzt können wir damit beginnen unseren Service Task zu implementieren.

 

Service Tasks implementieren

Um einen Service Task zu implementieren bietet Camunda verschieden Möglichkeiten an. Hier wollen wir uns erstmal nur die einfachsten Drei davon anschauen. Den anderen werde ich später in einem eigenen Artikel widmen.

Bevor wir uns ansehen wie wir unseren Code ausführen, müssen wir erst einmal festlegen was wir überhaupt ausführen wollen. Für diese Beispiel habe ich in der Anwendung eine Klasse mit dem Namen „CalculationService“ angelegt, welche auch als Bean im Spring Context registriert ist. Dieser Service enthält nur ein einzige Methode, welche nichts anders tut als einen Eintrag ins Logfile zu schreiben:

public void doCalculation() {
    log.info("Calculation service is executing calculation.");
}

Schauen wir uns jetzt an welche Möglichkeiten Camunda uns bietet, um diese Methode aus dem Prozess heraus aufzurufen. Dafür wählen wir den Service Task aus und schauen und das Dropdown „Implementation“ im Properties Panel an. Dort interessieren uns erstmal die folgenden Optionen:

  • Java Class
  • Expression
  • Delegate Expression

 

Service Task als „Java Class“ implementieren

Wenn wir Java Class als Implementierung wählen, müssen wir im nun erscheinenden Textfeld den Namen einer Java Klasse angeben.

Diese Klasse muss sich im Classpath unserer Anwendung befinden und sie muss das Interface JavaDelegate implementieren. Also legen wir die folgende Klasse an:

public class DoCalculationDelegate implements JavaDelegate {
    public void execute(DelegateExecution delegateExecution) throws Exception {
    }
}

Jetzt können wir im Modeler als Java Class den qualifizierten Namen unsere Klasse angeben. In meinem Beispiel Projekt wäre das:

de.pragtics.demos.servicetasks.delegates.DoCalculationDelegate

Die Process Engine wird jetzt, sobald sie unseren Task ausführt, die execute Methode in unserem Delegate aufrufen. Dort können wir dann ganz einfach unseren Service aufrufen.

new CalculationService().doCalculation()

Als erfahrene Softwareentwickler/ -innen werdet ihr hier die Augen verdrehen, weil ich den Service einfach mit dem new Keyword erzeuge. In einem echten Projekt würde man sowas wahrscheinlich eher nicht machen, da der Service meistens irgendwelche anderen Abhängigkeiten hat, welche aufgelöst werden müssen. Zum Glück hilft uns die Spring Integration von Camunda auch hier weiter.

Bevor Camunda unsere Delegate Klasse instanziiert, prüft die Engine nämlich ob sich im Spring Context bereits eine Bean mit diesem Typ befindet. Sollte das der Fall sein, wird einfach die bestehende Bean verwendete. Das bedeutet, dass wir nur eine @Component Annotation über unser Delegate schreiben müssen, und sofort können wir auf alle Spring Mechanismen wie z.B. die Dependency Injection zugreifen.

Die fertige Klasse würde dann folgendermaßen aussehen:

@Component
public class DoCalculationDelegate implements JavaDelegate {

    private CalculationService calculationService;

    public DoCalculationDelegate(CalculationService calculationService) {
        this.calculationService = calculationService;
    }

    public void execute(DelegateExecution delegateExecution) throws Exception {
        this.calculationService.doCalculation();
    }
}

Wenn wir jetzt die Anwendung ausführen und im Webinterface eine neue Prozessinstanz starten, sollten wir im Log die folgende Zeile wiederfinden:

INFO 81909 --- [nio-8080-exec-9] d.p.d.s.services.CalculationService : Calculation service is executing calculation.

 

Service Task als „Expression“ implementieren

Wie ihr vielleicht selber gemerkt habt, ist der Overhead für den Aufruf einer einzelnen Methode im letzten Beispiel recht hoch. Deshalb bietet Camunda mit der Expression eine schlankere Möglichkeit um Service Tasks zu implementieren.

Hier können wir unseren Code direkt im Modeler in das Feld Expression schreiben, wobei der komplette Ausdruck mit ${} eingeschlossen werden muss. Standardmäßig verwendet Camunda hier JUEL als Sprache für die Expressions, es lassen sich allerdings auch andere Skriptsprachen konfigurieren.

Dank der Spring Integration können wir innerhalb der Expression auf alle Beans im Context unserer Anwendung zugreifen. Dafür müssen wir sie einfach nur mit dem Namen der Bean referenzieren. Mit der folgenden Expression können wir also ganz einfach unseren Service aufrufen:

${calculationService.doCalculation()}

Starten wir jetzt wieder eine Prozessinstanz, sehen wir immer noch den erwarteten Eintrag im Logfile.

Expressions haben den Vorteil, dass sie das Anlegen von Delegates mit nur einer Zeile Code verhindern. Sobald die Delegates allerdings mehr als nur einen Service Aufruf enthalten, sollten diese als Klassen implementiert werden. Ansonsten würden wir die Logik der Delegation in eine andere Schicht verschieben und diese ggf. unnötig belasten.

 

Service Task als „Delegate Expression“ implementieren

Genau wie beim Typ „Expression“ wird auch bei der Delegate Expression eine JUEL Expression verwendet. Allerdings erwartet die Engine hier als Rückgabewert ein Objekt welches das JavaDelegate implementiert. Die Engine wird dann im nächsten Schritt die execute() Methode dieses Delegates aufrufen.

Auch hier könne wir wieder auf Bean im Context der Anwendung zugreifen. Die folgende Expression gibt einfach die Bean, welche wir für die Implementierung als „Java Class“ verwendet haben, zurück.

${doCalculationDelegate}

Auch hier sehen wir wieder unsere gewünschte Logausgabe, wenn wir einen Prozessinstanz starten.

Ein Anwendungsfall bei dem die Delegate Expression nützlich sein kann, wäre die Auswahl einer speziellen Bean anhand ihres Namens. Dadurch kann eine Implementierung von JavaDelegate einfacher wiederverwendet werden.

 

Fazit

Ich hoffe dass ihr mit diesem Artikel einen groben Überblick über die Implementierung von Service Tasks bekommen habt. Welche der vorgestellten Methoden verwendet werden soll, muss immer im Einzelfall entschieden werden, da alle Möglichkeiten Vor- und Nachteile haben.

Wie immer gibt es auch zu diesem Artikel ein Beispielprojekt in dem die drei Möglichkeiten demonstriert werden: https://bitbucket.org/pragtics/camunda-demos/src/master/service-tasks/