Externe Aufgaben von Camunda - ein leistungsstarkes Tool zum Erstellen von Anwendungen mit einer stabilen und skalierbaren Architektur

Bild

In Tinkoff verwenden wir das Camunda + Spring- Framework, um Geschäftsprozessautomatisierungssysteme zu entwickeln . Wir beschreiben Geschäftsprozesse mit BPMN (Business Process Management Notation) in Form von Flussdiagrammen.

Das am häufigsten verwendete Element in unseren Diagrammen sind Serviceaufgaben (Zahnradrechteck). Camunda unterstützt zwei Möglichkeiten zur Ausführung von Serviceaufgaben :

  1. Verwenden eines synchronen Aufrufs von Java-Code.
  2. Eine externe Aufgabe erstellen.

Mit der zweiten Methode können Sie Aufgaben mit externen Systemen ausführen, z. B. wenn Sie eine Camunda-Anwendung von einer anderen aufrufen oder sogar Arbeiten an ein externes System delegieren müssen.

Bild
Beispiel für ein BPMN-Diagramm

Dies ist nützlich, wenn Sie die Logik in mehreren Anwendungen wiederverwenden möchten. Oder wenn Sie sich an die Microservice-Architektur halten möchten. Trennen Sie beispielsweise Dienste, die sich mit Geschäftsprozessen befassen, von Diensten, die technische Aufgaben wie das Generieren von Berichten oder das Versenden von Nachrichten implementieren.

Zusammen mit dieser Funktion bietet eine externe Aufgabe eine skalierbare, ausfallsichere Architektur. Um zu verstehen, warum dies geschieht, müssen Sie zunächst verstehen, wie die externe Aufgabe auf BPMN- und Anwendungsebene funktioniert .

Externe Aufgabe in BPMN


Bei einer externen Aufgabe wird eine Aufgabe erstellt, die von einem externen Handler ausgeführt werden kann. Das Wesentliche des externen Aufgabenmusters ist:

  1. , «» , «».
  2. camunda , , .
  3. camunda (/).

In der obigen Abbildung habe ich einen fiktiven Prozess beschrieben, bei dem wir eine Liste der Benutzer erhalten, ihnen eine Anzeige senden und nach 2 Stunden die Anzahl der Bewerbungen nach dem Marketing-Newsletter berechnen möchten. Wenn mehr als 10 Anwendungen vorhanden sind, erhöhen Sie die Auswahl für das nächste Mailing.

Ich möchte, dass meine Camunda- Anwendung nur für Geschäftsprozesse verantwortlich ist, und jede andere Anwendung sollte für das Versenden von E-Mails verantwortlich sein. In diesem Fall funktioniert das externe Aufgabenmuster für mich einwandfrei . In meinem Prozess erstelle ich einfach eine E-Mail-Newsletter-Aufgabe und warte, bis sie von einem externen Handler abgeschlossen wurde.

Um eine externe Aufgabe im Diagramm zu erstellen , müssen Sie:

  1. Erstellen Sie eine reguläre Aufgabe .
  2. Ändern Sie den Typ in Serviceaufgabe .
  3. Setzen Sie die Implementierung auf extern .
  4. Geben Sie den Wert des Felds Thema an .

Bild

Thema ist der Name der Warteschlange, in die Aufgaben eines Typs hinzugefügt werden und die ein externer Handler abonniert.

Jetzt, da sich eine externe Aufgabe im Prozess befindet , können Sie sie starten, sie wird jedoch nicht ausgeführt, da sie von niemandem verarbeitet wird.

Mitarbeiter für externe Aufgaben


Das externe Aufgabenmuster ist insofern gut, als Sie die Aufgabenverarbeitung in jeder Sprache mit allen Tools implementieren können, die HTTP-Anforderungen ausführen können .

Das Folgende ist ein Beispiel aus dem Camunda- Blog . Das Beispiel implementiert einen externen Javascript- Handler , der alle 20 Sekunden eine Liste der Verarbeitungsaufgaben von camunda anfordert . Wenn es Aufgaben gibt, werden diese gesendet und Camunda über den Abschluss der Aufgabe informiert .

const baseUrl = 'http://localhost:8080/my-app/rest';
const workerSettings = {
 workerId: 'worker01', // some unique name for the current worker instance
 maxTasks: 5,
 topics: [
   {
     topicName: 'sendEmail',
     lockDuration: 10000, // How much time the worker thinks he needs to process the task
     variables: ['video'] // Which variables should be returned in the response (to avoid additional REST calls to read data)
   }]};
const requestParams = {method: 'POST', headers: {contentType: 'application/json'}};

function pollExternalTasks() {
 return fetch(`${baseUrl}/external-task/fetchAndLock`, {
   ...requestParams,
   body: JSON.stringify(workerSettings)
 })
}

function processExternalTask(result = []) {
 return Promise.all(result.map(externalTask => {
   sendEmail(externalTask); // Here the actual work would be done

   return fetch(`${baseUrl}/external-task/${externalTask.id}/complete`, {
     ...requestParams,
     body: JSON.stringify({workerId: workerSettings.workerId}),
   })
 }));
}

setInterval(() => {
 pollExternalTasks().then(processExternalTask)
}, 20000);

Wie Sie aus dem obigen Code sehen können, die wichtigsten Methoden für den Umgang mit externen Aufgaben sind fetchAndLock und vollständig . Die erste Methode fordert eine Liste von Aufgaben an und sichert deren Implementierung. Die zweite Methode informiert über den Abschluss der Aufgabe. Neben diesen beiden Methoden gibt es noch andere, die Sie in der offiziellen Dokumentation nachlesen können .

Camunda externer Task-Client


Bild

Um die Verarbeitung externer Aufgaben zu implementieren, stellte camunda Javascript- und Java- Clients zur Verfügung , mit denen Sie externe Aufgabenhandler in nur wenigen Zeilen erstellen können. Es gibt auch eine ausführliche Anleitung , die die Grundprinzipien der Verarbeitung externer Aufgaben beschreibt - wiederum mit Beispielen in Javascript und Java .

Eine Beispielimplementierung eines externen Handlers mit ExternalTaskClient :

public class App {
   public static void main(String... args) {
       // bootstrap the client
       ExternalTaskClient client = ExternalTaskClient.create()
           .baseUrl("http://localhost:8080/engine-rest")
           .asyncResponseTimeout(1000)
           .build();

       // subscribe to the topic
       client.subscribe("sendEmail").handler((externalTask, externalTaskService) -> {
           try {
               String result = sendEmail(externalTask)
               Map<String, Object> variables = new HashMap<>();

               variables.put("result", result);
               externalTaskService.complete(externalTask, variables);
               System.out.println("The External Task " + externalTask.getId() + " has been completed!");
           } catch (e: Exception) {
               externalTaskService.handleFailure(externalTask, e.message, e.stackTrace.toString())
           }
       }).open();
   }
}

Wenn für Ihre Aufgabe nicht nur eine synchrone Aktion ausgeführt werden muss, sondern der gesamte Prozess gestartet werden muss, können Sie dies beispielsweise tun, indem Sie den Prozess über RuntimeService starten :

@Service
class EmailWorker(
   private val runtimeService: RuntimeService
) {
   val builder = ExternalTaskClientBuilderImpl().baseUrl("http://localhost:8080").workerId("myWorker")
   val taskClient = builder.build()
   val engineClient = (builder as ExternalTaskClientBuilderImpl).engineClient

   @PostConstruct
   fun init() {
       taskClient
           .subscribe("sendEmail")
           .lockDuration(10000)
           .handler { externalTask, externalService ->
               runtimeService.startProcessInstanceByKey(
                   "SendEmailProcess",
                   externalTask.getVariable("emailId"),
                   mapOf(
                       "text" to externalTask.getVariable("text"),
                       "email" to externalTask.getVariable("email")
                   )
               )
           }
           .open()
   }


   @PreDestroy
   fun destroy() {
       taskClient.stop()
   }
}

// Delegate from SendEmailProcess process
@Component
class EmailResultDelegate(private val emailWorker: EmailWorker) {
   fun doExecute(execution: DelegateExecution) {
       emailWorker.engineClient.complete(
           execution.readVar(EXTERNAL_TASK_ID),
           mapOf("result" to "Success")
       )
   }
}

In diesem Beispiel startet der externe Aufgabenhandler ( EmailWorker ) beim Empfang der Aufgabe den SendEmailProcess- Prozess .

Stellen Sie sich vor, dieser Prozess führt einige Aktionen aus, die zum Senden des Newsletters erforderlich sind, und ruft schließlich EmailResultDelegate auf , wodurch die externe Aufgabe abgeschlossen wird .

Architektonische Vorteile externer Aufgaben


Es ist erwähnenswert, dass es eine Möglichkeit gibt, den Prozess in einer anderen Camunda- Anwendung auf einfachere Weise zu starten : POST: / rest / Prozessdefinition / key / $ {id} / start

Wenn Sie REST verwenden , haben Sie keine Transaktionsgarantien. Wir arbeiten aber auch mit externen Aufgaben über REST . Was ist dann der Unterschied?

Der Unterschied besteht darin, dass wir den externen Dienst nicht direkt aufrufen, sondern nur Aufgaben veröffentlichen, die verarbeitet werden können. Stellen Sie sich ein Beispiel vor:

Bild

Ein externer Handler übernimmt eine Aufgabe, die ihm jetzt zugewiesen ist. Wenn jedoch eine Antwort empfangen wird, wird die Verbindung getrennt. Jetzt auf der Seite der CamundaEine Aufgabe, die nicht verarbeitet wird, wird blockiert, da der externe Handler keine Antwort erhalten hat. Dies ist jedoch nicht beängstigend: In Camunda für externe Aufgaben gibt es eine Zeitüberschreitung, mit der die Aufgabe wieder in die Warteschlange zurückkehrt und jemand anderes damit umgehen kann.

Betrachten wir nun den Fall, in dem ein externer Handler eine Aufgabe empfangen, abgeschlossen und die vollständige Methode aufgerufen hat , die aufgrund von Netzwerkproblemen fehlgeschlagen ist. Jetzt können Sie nicht mehr verstehen, ob die Aufgabe in Camunda erfolgreich abgeschlossen wurde oder nicht. Sie können es erneut versuchen, es besteht jedoch die Möglichkeit, dass Netzwerkprobleme weiterhin auftreten.

In diesem Fall wäre die beste Lösung, das Problem zu ignorieren. Wenn die Aufgabe erfolgreich abgeschlossen wurde, ist alles in Ordnung. Wenn nicht, steht die Aufgabe nach dem Timeout wieder zur Bearbeitung zur Verfügung. Dies bedeutet jedoch, dass Ihr externer Handler idempotent sein oder Logik zum Deduplizieren von Aufgaben enthalten muss.

Ein ähnliches Problem kann beim Starten eines neuen Prozesses auftreten. Zuvor sollten Sie vorhandene Instanzen mit denselben Daten überprüfen, z. B. businessKey .

Neben hoher Fehlertoleranz externe AufgabeMit dieser Option können Sie externe Handler einfach skalieren und in einer beliebigen Programmiersprache implementieren. Gleichzeitig hilft das Muster, Mikrodienste so zu implementieren, dass sie sich so wenig wie möglich gegenseitig beeinflussen, wodurch ihre Stabilität erhöht wird.

Weitere Informationen zu externen Aufgaben :
https://docs.camunda.org/manual/latest/user-guide/process-engine/external-tasks/
https://docs.camunda.org/manual/latest/reference/rest/external -task /
https://docs.camunda.org/manual/latest/user-guide/ext-client/

All Articles