Tâches externes Camunda - un outil puissant pour créer des applications avec une architecture résiliente et évolutive

image

À Tinkoff, nous utilisons le framework Camunda + Spring pour développer des systèmes d'automatisation des processus métier . Nous décrivons les processus métier à l'aide de BPMN (Business Process Management Notation) sous forme d'organigrammes.

L'élément le plus couramment utilisé dans nos diagrammes est les tâches de service (rectangle d'engrenage). Camunda prend en charge deux façons d'effectuer des tâches de service :

  1. Utilisation d'un appel synchrone au code java.
  2. Création d'une tâche externe.

La deuxième méthode vous permet d'effectuer des tâches à l'aide de systèmes externes - par exemple, si vous devez appeler une application camunda à partir d'une autre ou même déléguer du travail à un système externe.

image
Exemple de diagramme BPMN

Ceci est utile lorsque vous avez l'intention de réutiliser la logique dans plusieurs applications. Ou lorsque vous souhaitez vous en tenir à l'architecture de microservices. Par exemple, séparer les services qui traitent des processus métier des services qui mettent en œuvre des tâches techniques, telles que la génération de rapports ou le publipostage.

Parallèlement à cette fonctionnalité, une tâche externe fournit une architecture évolutive et résiliente. Pour comprendre pourquoi cela se produit, vous devez d'abord comprendre comment fonctionne la tâche externe au niveau du BPMN et de l'application.

Tâche externe dans BPMN


La tâche externe implique la création d'une tâche qui peut être effectuée par un gestionnaire externe. L'essence du modèle de tâche externe est que:

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

Dans le diagramme ci-dessus, j'ai décrit un processus fictif dans lequel nous voulons obtenir une liste d'utilisateurs, leur envoyer une publicité et après 2 heures calculer le nombre d'applications après la newsletter marketing. Et, s'il y a plus de 10 demandes, augmentez la sélection pour le prochain envoi.

Je souhaite que mon application camunda soit uniquement responsable des processus métier, et toute autre application devrait être responsable de l'envoi par e-mail. Dans ce cas, le modèle de tâche externe fonctionne bien pour moi . Dans mon processus, je vais simplement créer une tâche de newsletter par e-mail et attendre qu'elle soit terminée par un gestionnaire externe.

Pour créer une tâche externe dans le diagramme , vous devez

  1. Créez une tâche régulière .
  2. Changez son type en tâche de service .
  3. Définissez l'implémentation sur externe .
  4. Spécifiez la valeur du champ Sujet .

image

La rubrique est le nom de la file d'attente dans laquelle les tâches d'un type seront ajoutées et auxquelles un gestionnaire externe s'abonnera.

Maintenant qu'il y a une tâche externe dans le processus , vous pouvez la démarrer, mais elle ne sera pas exécutée, car personne ne la traite.

Travailleur des tâches externes


Le modèle de tâche externe est bon en ce qu'il vous permet d'implémenter le traitement des tâches dans n'importe quelle langue, en utilisant tous les outils qui peuvent effectuer des requêtes HTTP .

Ce qui suit est un exemple du blog de camunda . L'exemple implémente un gestionnaire javascript externe qui, toutes les 20 secondes, demande une liste de tâches de traitement à camunda . S'il y a des tâches, il les envoie et informe la camunda de l'achèvement de la tâche.

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);

Comme vous pouvez le voir dans le code ci-dessus, les principales méthodes de gestion des tâches externes sont fetchAndLock et complete . La première méthode demande une liste de tâches et sécurise leur implémentation, et la seconde informe de l'achèvement de la tâche. En plus de ces deux méthodes, il y en a d'autres, vous pouvez les lire dans la documentation officielle .

Client de tâches externes Camunda


image

Pour implémenter le traitement des tâches externes , camunda a fourni des clients Javascript et Java qui vous permettent de créer des gestionnaires de tâches externes en quelques lignes seulement. Il existe également un guide détaillé qui décrit les principes de base du traitement des tâches externes - encore une fois avec des exemples en Javascript et Java .

Un exemple d'implémentation d'un gestionnaire externe utilisant 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();
   }
}

Si votre tâche nécessite non seulement d'effectuer une action synchrone, mais de démarrer l'ensemble du processus, vous pouvez le faire, par exemple, en démarrant le processus via RuntimeService :

@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")
       )
   }
}

Dans cet exemple, le gestionnaire de tâches externes ( EmailWorker ), lorsque la tâche est reçue, démarre le processus SendEmailProcess .

Imaginez que ce processus effectue certaines actions nécessaires pour envoyer la newsletter, et à la fin appelle EmailResultDelegate , qui, à son tour, termine la tâche externe .

Avantages architecturaux d'une tâche externe


Il convient de noter qu'il existe un moyen de démarrer le processus dans une autre application camunda de manière plus simple: POST: / rest / process-definition / key / $ {id} / start

Lorsque vous utilisez REST , vous n'avez aucune garantie de transaction. Mais après tout, nous travaillons également avec des tâches externes via REST , quelle est la différence alors?

La différence est que nous n'appelons pas directement le service externe, mais publions uniquement les tâches qui peuvent être traitées. Prenons un exemple:

image

un gestionnaire externe prend une tâche qui lui est désormais affectée, mais lorsqu'une réponse est reçue, la connexion est déconnectée. Maintenant du côté de la camundaune tâche qui ne sera pas traitée est bloquée car le gestionnaire externe n'a pas reçu de réponse. Mais ce n'est pas effrayant: dans la camunda pour les tâches externes, il y a un délai d'expiration par lequel la tâche retournera à nouveau dans la file d'attente et quelqu'un d'autre pourra la gérer.

Examinons maintenant le cas où un gestionnaire externe a reçu une tâche, l'a terminée et a appelé la méthode complete , qui a échoué en raison de problèmes de réseau. Maintenant, vous ne pourrez pas comprendre si la tâche s'est terminée avec succès dans camunda ou non. Vous pouvez réessayer, mais il est possible que les problèmes de réseau persistent.

Dans ce cas, la meilleure solution serait d'ignorer le problème. Si la tâche s'est terminée avec succès, tout est en ordre. Sinon, après la temporisation, la tâche sera à nouveau disponible pour le traitement. Mais cela signifie que votre gestionnaire externe doit être idempotent ou contenir une logique pour dédupliquer les tâches.

Un problème similaire peut se produire lors du démarrage d'un nouveau processus, vous devez donc vérifier les instances existantes avec les mêmes données, par exemple, businessKey .

En plus d'une tâche externe à haute tolérance aux pannesvous permet de mettre à l'échelle facilement des gestionnaires externes et de les implémenter dans n'importe quel langage de programmation. Dans le même temps, le motif permet de mettre en œuvre des microservices afin qu'ils s'influencent le moins possible, augmentant ainsi leur stabilité.

En savoir plus sur la tâche externe :
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