Na DIRECTUM, estamos desenvolvendo o sistema DirectumRX ECM. O elemento principal do módulo Workflow para o sistema ECM é o mecanismo. Ele é responsável por alterar o estado da instância do processo (instância) durante o ciclo de vida. Antes de começar a desenvolver o módulo Workflow, você deve decidir: pegue um mecanismo pronto ou escreva seu próprio. Inicialmente, optamos pela primeira opção. Adotamos o mecanismo Windows Workflow Foundation (WF) e, no geral, ele nos convinha. Mas, com o tempo, percebemos que precisávamos de nosso próprio mecanismo. Como isso aconteceu, e o que aconteceu, vou contar abaixo.
Motor antigo
Por que wf?
Em 2013, quando era hora de desenvolver um módulo de fluxo de trabalho para o DirectumRX, decidimos usar um mecanismo pronto. Visto do Windows Workflow Foundation (WF), ActiveFlow, K2.NET, WorkflowEngine.NET, cDevWorkflow, NetBpm. Alguns não estavam satisfeitos com o custo, outros estavam brutos, outros, até então, não eram sustentados por um longo tempo.
Como resultado, a escolha recaiu sobre o WF. Em seguida, usamos ativamente a pilha da Microsoft (WCF, WPF) e decidimos que outro W não iria nos prejudicar. Outra vantagem foi nosso status como Microsoft Gold Application Development Partner, que tornou possível o desenvolvimento de produtos usando as tecnologias da Microsoft. Bem, em geral, os recursos do mecanismo nos adequavam e cobriam quase todos os nossos casos.
O que há de errado com o WF?
Após 6 anos de uso do WF, acumulamos vários problemas e o custo para resolvê-los era muito alto. Começamos a pensar em desenvolver nosso próprio mecanismo. Vou falar sobre alguns deles.
Diagnósticos caros e correções de bugs
Os anos se passaram, o número de instalações do produto e a carga aumentaram. Os bugs começaram a aparecer, cujo diagnóstico e correção exigiram muitos recursos. Isso foi facilitado por um complexo de razões: falta de competências, erros de design ao incorporar o mecanismo anterior e recursos do WF.
Tínhamos competências básicas suficientes para criar no WF DirectumRX, o mesmo nível era suficiente para lidar com bugs simples. Para casos complexos, faltavam competências - a análise de logs, a análise do estado da instância e assim por diante eram difíceis.
Foi possível enviar uma pessoa para cursos no WF, mas eles dificilmente aprendem como analisar o estado de uma instância e associar sua alteração aos logs. E, francamente, ninguém tinha um desejo particular de atualizar suas habilidades com a tecnologia praticamente morta.
Outra maneira é contratar uma pessoa com as competências apropriadas. Mas encontrar um em Izhevsk não é uma tarefa tão trivial, e não é o fato de que seu nível é suficiente para resolver nossos problemas.
De fato, somos confrontados com um alto limite de entrada para apoiar o WF. De uma forma ou de outra, acho que lidaríamos com esse problema, se não por várias outras razões.
Outro problema foi que, ao construir diagramas de processo, usamos nossa própria notação. É mais visual e mais fácil de desenvolver. Por exemplo, o WF não permite implementar um gráfico completo, você não pode desenhar blocos sem saída, existem recursos para desenhar ramificações paralelas. O retorno disso é a conversão de nossos circuitos em circuitos WF, que não são tão simples e impõem uma série de limitações. Ao depurar, tive que analisar o estado do circuito WF, por causa disso, a visibilidade foi perdida, tive que comparar blocos e faces entre si para entender em que etapa estava a instância.
Representação do circuito no DirectumRXRepresentação do circuito no WFAlém disso, fomos confrontados com o fato de que a documentação do WF descreve mal o repositório da instância. Como escrevi acima, isso é necessário ao analisar um bug para entender o estado da instância do processo. Além disso, parte dos dados é criptografada, o que também interfere na análise.
Postgres como um DBMS
Há muitos anos, há uma tendência na Rússia de substituição de importações, e cada vez mais um dos requisitos para a plataforma é o suporte de sistemas de gerenciamento de banco de dados de código aberto (DBMS) ou DBMSs da produção doméstica. Na maioria das vezes é o Postgres. Fora da caixa, o WF suporta apenas o MS SQL. Para trabalhar com outros bancos de dados, você pode usar provedores de terceiros. Escolhemos o dotConnect do DevArt.
Enquanto a carga era leve, tudo funcionou bem. Mas assim que dirigimos o sistema sob carga, surgiram problemas. O WF pode parar subitamente e parar de processar instâncias (transações preparadas finalizadas) ou todas as mensagens foram para a Fila Envenenada do MSMQ etc. Lidamos com todos esses problemas, mas gastamos muito tempo com isso. Não havia garantia de que um novo não aparecesse, cuja solução teria que gastar a mesma quantia.
Cuidados no núcleo .net
Depois que a Microsoft anunciou o .Net Core, decidimos que iríamos gradualmente a ele para obter plataforma cruzada para nossas soluções. A Microsoft decidiu não levar o WF a bordo, o que impediu a transferência do módulo Workflow para o .Net Core na forma em que ele existia. Estamos cientes de que existem portas WF não oficiais no .Net Core e, dentre elas, existem até desenvolvedores de WF, mas nem todas são 100% compatíveis. Outro fator foi a recusa da Microsoft em desenvolver .Net. a favor do .Net Core.
Novo motor
Tomando todo esse monte de problemas, opções de solução, complexidade de refatoração e correções, pesando todos os prós e contras, decidimos mudar para um novo mecanismo. Começamos analisando os existentes.
A escolha
Os principais requisitos ao escolher um mecanismo foram:
- trabalho no .Net Core;
- escalabilidade
- conversão de instâncias de processo existentes, com a capacidade de continuar a execução após a conversão
- custo razoável de analisar problemas existentes
- trabalhar com diferentes DBMS
Além disso, era necessário que a Activity (Activity) pudesse executar o código do aplicativo em C #, era possível depurar blocos etc.
Como parte da análise dos mecanismos existentes, analisamos:
- Core wf
- Flowwright
- Fluxo de trabalho K2
- Núcleo do fluxo de trabalho
- Zeebe
- Mecanismo de fluxo de trabalho
- Estrutura de tarefas durável
- Camunda
- Atividades em Orleans
Tendo imposto todos os requisitos às soluções revisadas e acrescentado o custo das soluções pagas, consideramos que nosso mecanismo não é muito caro, mas será 100% adequado para nossas solicitações e será fácil refinar.
Implementação / Arquitetura
Na implementação anterior, o módulo WF era um serviço WCF ao qual as bibliotecas WF estavam conectadas. Ele foi capaz de criar instâncias de processo, iniciar processos, executar blocos, incluindo lógica de negócios (código escrito pelos desenvolvedores). Tudo isso foi hospedado no aplicativo IIS.
Na nova implementação, seguindo a tendência da arquitetura de microsserviço, decidimos dividir imediatamente o serviço em dois: Serviço de Processo de Fluxo de Trabalho (WPS) e Serviço de Bloco de Fluxo de Trabalho (WBS), que poderiam ser hospedados separadamente. Outro link nessa cadeia é o Serviço de Aplicativo, que implementa o sistema DirectumRX e a lógica de negócios, e os clientes trabalham com ele.
O WPS “caminha” de acordo com o esquema, o WBS processa blocos e executa a lógica de negócios a cada passo. O comando para iniciar o processo vem do Application Server. A interação entre os serviços é realizada usando o RabbitMQ. Abaixo, vou falar mais sobre cada um deles.
Wps
O Serviço de Processo de Fluxo de Trabalho é um microsserviço responsável por iniciar processos e ignorar o diagrama de processo.
O repositório de serviços contém diagramas de processo com suporte para controle de versão e estado serializado de instâncias de processo. Você pode usar o MS SQL e o Postgres como armazenamento.
O serviço é capaz de processar mensagens recebidas de outros serviços através do RabbitMQ. Essencialmente, as mensagens são uma API de serviço. Tipos de mensagens que o serviço pode receber:
- StartProcess - crie uma nova instância de processo e inicie um rastreamento nela;
- CompleteBlock - conclusão do bloco, após esta mensagem, o serviço move a instância do processo ainda mais ao longo do esquema;
- Suspend / ResumeProcess - suspende a execução de uma instância de processo, por exemplo, devido a um erro durante o processamento de um bloco e retoma a execução após a correção do erro;
- Abortar / RestartProcess - interrompa a execução da instância do processo e inicie-a novamente;
- DeleteProcess - exclua uma instância do processo.
O esquema consiste em blocos e conexões entre eles (faces). Cada face possui um identificador, o chamado "Resultado da Execução". Existem 5 tipos de blocos:
- StartBlock
- Bloquear
- OrBlock;
- AndBlock;
- FinishBlock.
Visualização de esquema WPSQuando uma mensagem chega no início do processo, o serviço cria uma instância e começa a "andar" de acordo com o esquema. A classe responsável pela “caminhada” de acordo com o esquema, chamamos de brincadeira de “Stepator”. Um circuito sempre começa com um StartBlock. Então o strider pega todas as faces de saída e as ativa. Cada bloco trabalha com o princípio do bloco "AND", ou seja, todas as faces recebidas devem estar ativas para que o bloco possa ser ativado. O algoritmo decide quais blocos podem ser ativados e envia uma mensagem PEP para ativar esses blocos. A WBS processa o bloco e retorna o resultado do WPS. Dependendo do resultado da execução, o strider seleciona as faces apropriadas que saem do bloco para ativação e o processo continua.
Durante o desenvolvimento, nos deparamos com situações interessantes relacionadas a conexões cíclicas entre blocos, que adicionavam lógica ao decidir qual bloco ativar / parar.
O serviço é autônomo, ou seja, basta passar a ele o esquema no formato Json, escreva seu próprio manipulador de bloco e você poderá trocar mensagens.
Wbs
O Serviço de Bloco de Fluxo de Trabalho é um serviço que processa diagramas de bloco. O serviço conhece a essência da lógica de negócios, como tarefa, tarefa etc. Essas entidades podem ser adicionadas ao ambiente de desenvolvimento do DirectumRX Development Studio (DDS). Por exemplo, nossos blocos têm um evento para iniciar o bloco. O código para este manipulador de eventos é escrito pelo desenvolvedor no DDS e o WBS executa esse código. De fato, esta é a nossa implementação do manipulador de blocos; você pode substituí-lo pelo seu.
O serviço armazena o estado dos blocos. Além das propriedades básicas (Id, State), o bloco pode conter outras informações necessárias para a execução / término / suspensão do bloco.
Os blocos podem estar em um estado:
- Concluído - entra nesse estado após a conclusão bem-sucedida do trabalho no bloco;
- Pendente - está em um estado de espera quando algum trabalho está sendo executado dentro do bloco, por exemplo, algum tipo de resposta é necessária ao usuário;
- Abortado - entra nesse estado quando o processo é interrompido;
- Suspenso - entra nesse estado quando o processo para quando ocorre um erro.
Quando uma mensagem chega na execução do bloco, o bloco é executado e a EAP envia uma mensagem com o resultado da execução do bloco.
Escalabilidade
O WPS e o WBS podem ser implantados em várias instâncias. Em um determinado momento, apenas um serviço WPS pode processar uma instância de processo. O mesmo se aplica ao processamento de blocos - uma instância de processo pode processar apenas um bloco por vez. Isso é ajudado por bloqueios que são colocados no processo durante o processamento. Se houver várias mensagens na fila para processar um processo / blocos em um processo, a mensagem será adiada por algum tempo. Ao mesmo tempo, cada serviço pode executar simultaneamente o trabalho em várias instâncias do processo.
Uma situação pode surgir quando várias mensagens chegam em um processo após o outro para processar blocos (ramificações paralelas). Para reduzir o número de situações em que é necessário adiar mensagens, o WBS recebe várias mensagens por vez e as executa uma após a outra, ignorando o envio para a fila para execução repetida devido ao bloqueio do processo.
Conversão
Após a transição para um novo mecanismo, surgiu a pergunta, o que fazer com as instâncias de processo existentes? A opção preferida foi a conversão, para que continuassem trabalhando no novo mecanismo. As vantagens são óbvias: suportamos apenas um mecanismo, os problemas de suporte ao mecanismo antigo desaparecem (veja acima). Mas havia riscos de que não conseguiríamos descobrir completamente como obter os dados necessários a partir de instâncias de processo serializadas. Houve também um recuo: dar instâncias existentes finalizadas no mecanismo antigo e lançar novas em um novo. As desvantagens dessa opção decorrem das vantagens da anterior, além de recursos adicionais necessários para torcer os dois motores.
Para a conversão, precisávamos pegar o antigo estado do processo no formato WF e gerar os estados dos processos e blocos. Escrevemos um utilitário que obteve o estado serializado de uma instância de processo no banco de dados, extraiu uma lista de blocos ativos, resultados de execução para faces e praticamente executou o processo. Como resultado, obtivemos o estado da instância no momento da conversão.
Surgiram dificuldades em como desserializar adequadamente os dados da instância do processo no WF. O estado da instância do processo (instância) do WF é armazenado no banco de dados como xaml. Não conseguimos encontrar uma descrição clara da estrutura desse xaml, tivemos que percorrer todo o caminho empiricamente. Analisou os dados manualmente e extraiu as informações necessárias. Como parte dessa tarefa, elaboramos outra opção - usando as ferramentas WF para desserializar o estado da instância e tentar obter informações dos objetos. Mas, devido ao fato de a estrutura de tais objetos ser muito complexa, abandonamos essa idéia e decidimos analisar xaml “manualmente”.
Como resultado, a conversão foi bem-sucedida e todas as instâncias do processo começaram a ser processadas pelo novo mecanismo.
Conclusão
Então, o que o mecanismo de fluxo de trabalho nos deu? Na verdade, conseguimos derrotar todos os problemas expressados no início do artigo:
- O mecanismo é escrito no .NET Core;
- é um serviço de auto-host independente do IIS;
- como operação de teste, usamos ativamente o novo mecanismo no sistema corporativo e conseguimos garantir que a análise de bugs leve muito menos tempo;
- realizou testes de carga no Postgres, de acordo com dados preliminares, o pacote WPS + WBS lida com a carga de 5000 usuários simultâneos sem problemas;
- e, claro, como qualquer trabalho interessante, é uma experiência interessante.
Como bônus, obtivemos um código claro e suportado que podemos adaptar a nós mesmos.
O custo do mecanismo acabou sendo comparável ao que teríamos que gastar na compra / adaptação de um produto de terceiros. No momento, acreditamos que a decisão de desenvolver seu próprio mecanismo acabou sendo justificada.
Também estamos aguardando testes de carga para mais de 10.000 usuários simultâneos. Talvez seja necessária alguma otimização ou talvez decole? ;-)
Lançamos recentemente o DirectumRX 3.2, que incluía o novo fluxo de trabalho. Vamos ver como o mecanismo se mostrará para os clientes.