Em Tinkoff, usamos a estrutura Camunda + Spring para desenvolver sistemas de automação de processos de negócios . Descrevemos processos de negócios usando o BPMN (Business Process Management Notation) na forma de fluxogramas.O elemento mais usado em nossos diagramas são as tarefas de serviço (retângulo de engrenagem). Camunda suporta duas maneiras de executar tarefas de serviço :- Usando uma chamada síncrona para código java.
- Criando uma tarefa externa.
O segundo método permite executar tarefas usando sistemas externos - por exemplo, se você precisar chamar um aplicativo camunda de outro ou mesmo delegar trabalho a qualquer sistema externo.
Exemplo de diagrama do BPMNIsso é útil quando você pretende reutilizar a lógica em vários aplicativos. Ou quando você deseja manter a arquitetura de microsserviço. Por exemplo, separando serviços que lidam com processos de negócios de serviços que implementam tarefas técnicas, como gerar relatórios ou correspondência.Juntamente com esse recurso, uma tarefa externa fornece uma arquitetura escalável e resiliente. Para entender por que isso acontece, primeiro você precisa entender como a tarefa externa funciona nos níveis de aplicativo e BPMN .Tarefa externa no BPMN
Tarefa externa envolve a criação de uma tarefa que pode ser executada por um manipulador externo. A essência do padrão de tarefa externa é que:- , «» , «».
- camunda , , .
- camunda (/).
No diagrama acima, descrevi um processo fictício no qual queremos obter uma lista de usuários, enviá-los um anúncio e após 2 horas calcular o número de aplicativos após o boletim de marketing. E, se houver mais de 10 aplicativos, aumente a seleção para a próxima correspondência.Quero que meu aplicativo camunda seja responsável apenas pelos processos de negócios e qualquer outro aplicativo deve ser responsável por enviar e-mails. Nesse caso, o padrão de tarefa externa funciona bem para mim . No meu processo, vou simplesmente criar uma tarefa de boletim informativo por email e aguardar que ela seja concluída por algum manipulador externo.Para criar uma tarefa externa no diagrama , você deve:- Crie uma tarefa regular .
- Altere seu tipo para tarefa de serviço .
- Defina a implementação como externa .
- Especifique o valor do campo Tópico .
Tópico é o nome da fila na qual as tarefas de um tipo serão adicionadas e às quais um manipulador externo se inscreverá.Agora que existe uma tarefa externa no processo , você pode iniciá-la, mas ela não será executada, pois ninguém a está processando.Trabalhador de tarefas externas
O padrão de tarefa externa é bom, pois permite implementar o processamento de tarefas em qualquer idioma, usando qualquer ferramenta que possa executar solicitações HTTP .A seguir, um exemplo do blog camunda . O exemplo implementa um manipulador javascript externo que, a cada 20 segundos, solicita uma lista de tarefas de processamento da camunda . Se houver tarefas, ele as envia e notifica a camunda sobre a conclusão da tarefa.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);
Como você pode ver no código acima, os principais métodos para lidar com tarefas externas são fetchAndLock e completos . O primeiro método solicita uma lista de tarefas e protege sua implementação, e o segundo informa sobre a conclusão da tarefa. Além desses dois métodos, existem outros, você pode ler sobre eles na documentação oficial .Cliente de tarefa externa Camunda
Para implementar o processamento de tarefas externas , a camunda forneceu clientes Javascript e Java que permitem criar manipuladores de tarefas externos em apenas algumas linhas. Há também um guia detalhado que descreve os princípios básicos do processamento de tarefas externas - novamente com exemplos em Javascript e Java .Um exemplo de implementação de um manipulador externo usando 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();
}
}
Se sua tarefa exigir não apenas executar alguma ação síncrona, mas iniciar todo o processo, você poderá fazê-lo, por exemplo, iniciando o processo por meio do 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")
)
}
}
Neste exemplo, o manipulador de tarefas externo ( EmailWorker ), quando a tarefa é recebida, inicia o processo SendEmailProcess .Imagine que esse processo execute algumas ações necessárias para enviar um boletim informativo e, no final, chame EmailResultDelegate , que, por sua vez, completa a tarefa externa .Benefícios arquitetônicos da tarefa externa
Vale ressaltar que existe uma maneira de iniciar o processo em outro aplicativo camunda de uma maneira mais simples: POST: / rest / definição do processo / chave / $ {id} / startQuando você usa o REST , você não tem nenhuma garantia de transação. Afinal, também trabalhamos com tarefas externas por meio do REST , qual é a diferença então?A diferença é que não chamamos o serviço externo diretamente, mas apenas publicamos tarefas que podem ser processadas. Considere um exemplo:
algum manipulador externo executa uma tarefa que agora está atribuída a ele, mas quando uma resposta é recebida, a conexão é desconectada. Agora do lado da camundauma tarefa que não será processada é bloqueada porque o manipulador externo não recebeu uma resposta. Mas isso não é assustador: na camunda para tarefas externas, existe um tempo limite pelo qual a tarefa retorna à fila novamente e outra pessoa pode lidar com isso.Agora, vejamos o caso em que um manipulador externo recebeu uma tarefa, a concluiu e chamou o método complete , que falhou devido a problemas de rede. Agora você não será capaz de entender se a tarefa foi concluída com êxito em camunda ou não. Você pode tentar novamente, mas há uma chance de os problemas de rede continuarem.Nesse caso, a melhor solução seria ignorar o problema. Se a tarefa foi concluída com êxito, tudo está em ordem. Caso contrário, após o tempo limite, a tarefa estará novamente disponível para processamento. Mas isso significa que seu manipulador externo deve ser idempotente ou conter lógica para desduplicar tarefas.Um problema semelhante pode ocorrer ao iniciar um novo processo, portanto, você deve verificar as instâncias existentes com os mesmos dados, por exemplo, businessKey .Além da tarefa externa de alta tolerância a falhaspermite escalar facilmente manipuladores externos e implementá-los em qualquer linguagem de programação. Ao mesmo tempo, o padrão ajuda a implementar microsserviços para que eles se influenciem o menos possível, aumentando assim sua estabilidade.Mais sobre tarefas externas :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/