À 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 :- Utilisation d'un appel synchrone au code java.
- 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.
Exemple de diagramme BPMNCeci 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:- , «» , «».
- camunda , , .
- 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- Créez une tâche régulière .
- Changez son type en tâche de service .
- Définissez l'implémentation sur externe .
- Spécifiez la valeur du champ Sujet .
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',
maxTasks: 5,
topics: [
{
topicName: 'sendEmail',
lockDuration: 10000,
variables: ['video']
}]};
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);
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
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) {
ExternalTaskClient client = ExternalTaskClient.create()
.baseUrl("http://localhost:8080/engine-rest")
.asyncResponseTimeout(1000)
.build();
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()
}
}
@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} / startLorsque 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:
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/