Construindo aplicativos invencíveis com Temporal e MongoDB
Tom Wheeler, Tim Kelly26 min read • Published Jan 27, 2025 • Updated Jan 27, 2025
APLICATIVO COMPLETO
Avalie esse Tutorial
Os últimos 15 anos transformaram a forma como abordamos o software. Os fornecedores de nuvem nos permitem provisionar infraestrutura em todo o mundo – sob demanda – e escalá-la à medida que nossas necessidades mudam. Os microsserviços nos dão maior flexibilidade para implementar, atualizar e dimensionar cada componente. Aplicativos modernos dependem de serviços interconectados, geralmente abrangendo diferentes redes, regiões ou até mesmo organizações. Aplicativos modernos são sistemas distribuídos.

Esses aplicativos são mais escaláveis do que suas contrapartes monolíticas, mas têm mais pontos potenciais de falha. Considere o aplicação básico e-commerce descrito acima, que depende de três serviços, cada um com seu próprio banco de dados. Há um serviço de estoque que fornece informações sobre os produtos disponíveis, um serviço de pagamento que cobra dos clientes pelas compras e um serviço de notificação que envia um e-mail atualizando os clientes sobre o status de seus pedidos.
O aplicação depende da disponibilidade de todos os três serviços, mas eles podem deixar de responder devido à carga pesada. Eles também podem sofrer uma interrupção de rede , passar por manutenção do sistema ou perder a conexão com o banco de dados .
Às vezes, os aplicativos enfrentam falta de confiabilidade na rede em seu próprio lado da conexão. Pior ainda, ele pode falhar devido a um bug de software ou falha de hardware. Se isso ocorrer no meio do processamento do pedido, os itens poderão ser removidos do estoque, mas o cliente nunca será cobrado. Se o aplicação for reiniciado, o cliente poderá receber e-mails de confirmação duplicados ou ser cobrado duas vezes por uma única compra. Talvez você já tenha passado por isso como cliente.
Neste tutorial, você aprenderá como evitar esses tipos de problemas usando uma plataforma de código aberto chamada Temporal, que permite que os aplicativos suportem interrupções de serviço e falhas de aplicação . Embora você não possa evitar que esses problemas ocorram, o Temporal lida com eles automaticamente, para que você possa codificar como se nunca acontecessem.
A Temporal oferece a Execução Durável, uma abstração que garante que seu aplicação continuará em execução apesar das condições adversas. Ele abstrai a complexidade da construção de sistemas distribuídos escaláveis, o que, por sua vez, reduz a quantidade de código que você precisa desenvolver, depurar e manter.
A plataforma Temporal consiste em duas partes, ambas de código aberto. O primeiro é uma biblioteca, conhecida como SDK temporal, que fornece as APIs e as implementações necessárias para escrever aplicativos temporais em uma linguagem de programação específica. Atualmente, a Temporal oferece SDKs para Java, Go, Typescript, Python, PHP e .NET. O segundo está o software de servidor , que rastreia o progresso e o estado de seus aplicativos. Se seu aplicação falhar, esses dados de histórico possibilitarão reconstruir o estado pré-falha em um novo processo e, em seguida, retomar a execução como se nunca tivesse falhado. Seu aplicação pode até superar falhas de hardware, pois esse processo de recuperação pode ocorrer em uma máquina diferente.
O Temporal tem suas origens em um sistema anterior que seus criadores desenvolveram enquanto trabalharam como engenheiros de software na Uber, que por sua vez era baseado em sistemas distribuídos de grande escala que eles criaram anteriormente na Amazon e na Microsoft. Atualmente, milhares de empresas usam o Temporal na produção, com uma gama diversificada de casos de uso, que inclui treinamento de modelos de IA, gerenciamento de pedidos, provisionamento de infraestrutura, registro de reservas e processamento de transações financeiros.
Imagine que a banda favorita de Ana esteja fazendo um show em sua cidade. Infelizmente, os ingressos foram colocados à venda quandoMaria estava fazendo uma apresentação importante para o CEO. Felizmente, seu amigo David envia uma mensagem para ela explicando que tem um lugar livre na primeira fila. Maria pega seu telefone e usa um aplicativo para transferir $100 do ticket de sua conta no Banco A para a conta de David no Banco B.
Do ponto de vista do usuário do aplicativo, mover dinheiro entre dois bancos é uma única operação. Na verdade, envolve duas etapas: remover o valor da conta da Ana e, em seguida, transferi-lo para a conta do David. Ambas as etapas devem acontecer e ambas devem acontecer exatamente uma vez.

Isso destaca a necessidade de o aplicação ser resiliente. Assim como no exemplo de e-commerce descrito acima, ele depende da disponibilidade de ambos os bancos, mas qualquer um pode sofrer uma interrupção por vários motivos. Uma falha no aplicação pode ter séries consequências.
Por exemplo, suponha que a energia saia entre as etapas de retirada e depósito. Nesse caso, os saldos das contas terão um desconto de $100, pois o dinheiro foi removido da conta de Maria, mas nunca foi depósitodo na conta de David. Como o encerramento de um aplicação limpa seu estado, o que significa que o programa não tem memória do que fez antes, reiniciar o aplicação não corrigirá isso. Se a transferência de dinheiro for concluída após ser reiniciada, ela removerá outros $100 da conta de Maria antes de despejar $100 na conta de David. Os $100 originais ainda estão faltando!
Durante este tutorial, você experimentará um aplicação de transferência de dinheiro implementado com o Temporal. Você verá por si mesmo como ele é capaz de superar uma interrupção de serviço e até mesmo uma falha do próprio aplicação . Você também verá como oferecer suporte a um cenário humano-in-the-loop, no qual a transferência aguarda a aprovação manual de um gerente antes de prosseguir.
Além do código do aplicação Temporal, fornecemos o código para simular os serviços bancários (embora, por uma questão de simplicidade, isso seja feito usando um único serviço e banco de dados). Fornecemos uma GUI que mostra os saldos atuais da conta e controla se o serviço está disponível para uma determinada conta, permitindo que você crie uma interrupção sob demanda.

- Java 11 ou superior
- Comece a usar o MongoDB Atlas gratuitamente! Use o cluster MongoDB M0 e acompanhe.
- Temporal CLI, uma ferramenta de linha de comando para usar o Temporal
O principal bloco de construção de um aplicação Temporal é chamado de Fluxo de Trabalho, que é uma função ou método que tem o benefício da Execução Durável. Uma vez iniciado, um fluxo de trabalho continuará sua execução até que seja bem-sucedido ou você opte por encerrá-lo.
Ao contrário dos mecanismos de fluxo de trabalho que favorecem uma abordagem de baixo ou nenhum código, o que necessariamente desabilita e restringe o desenvolvedor, um fluxo de trabalho temporal é definido pela escrita de código usando qualquer linguagem de programação suportada por um dos SDKs temporais. Essa abordagem de fluxos de trabalho como código significa que você pode escrever, testar, compilar e pacote um aplicação Temporal usando as mesmas ferramentas que você já usa.
O Temporal coloca algumas restrições sobre como você estrutura ou implementa seu código. Há uma exceção notável: o fluxo de trabalho deve ser determinístico. Isso significa que, para uma determinada entrada, ela deve fazer a mesma coisa todas as vezes. Por exemplo, se você criar um Fluxo de Trabalho que retorna a soma de dois números, passá-lo
5
e 7
como entrada deverá sempre retornar o valor 12
. Operações envolvendo números aleatórios são uma fonte explícita de comportamento não determinístico, mas interagir com sistemas externos também não é determinístico porque o resultado depende da disponibilidade e do comportamento desse sistema externo em um determinado momento no tempo. Você ainda pode executar operações não determinísticas em um aplicação Temporal ; você só precisa colocar esse código em uma Atividade.As atividades encapsulam código sujeito a falhas. Se uma atividade falhar, ela será automaticamente repetida de acordo com uma política personalizável. Essas novas tentativas permitem que um aplicação Temporal suporte interrupções de serviço e outras falhas transitórias ou intermitentes. Quando a falha for resolvida - independentemente do tempo necessário - a próxima tentativa será bem-sucedida e o aplicação continuará como se a falha nunca tivesse ocorrido. O Temporal também oferece suporte integrado para pulsações e tempos limite, permitindo a rápida detecção de falhas mesmo em operações de longa duração.
Conforme explicado anteriormente, o Temporal tem uma biblioteca do lado do cliente (o SDK do Temporal) e um software de servidor . O Serviço Temporal é iniciado executando esse software de servidor , independentemente de onde ele esteja sendo executado. Ele tem duas funções principais. A primeira é manter as filas de tarefas usadas para agendar o trabalho a ser feito e a segunda é manter um histórico de eventos para o trabalho já concluído.
Depois que um trabalhador executa o código correspondente à tarefa, ele relata o resultado ao serviço temporal, que registra os detalhes no histórico de eventos. Se essa Tarefa for para uma Atividade que falhou ou atingiu o tempo limite, o Serviço Temporal agendará outra Tarefa para a nova tentativa. Caso contrário, ele continuará adicionando novas Tarefas até que a Execução do Fluxo de Trabalho seja concluída.
Esta é uma ferramenta de linha de comando para interagir com o Temporal. Ele também fornece uma versão leve do Temporal Service que é útil para o desenvolvimento local, que você usará para este tutorial.
Este diagrama ilustra como todos os itens acima se relacionam uns com os outros. A linha vertical pontilhada indica a separação entre o Trabalhador e o Serviço Temporal. Neste tutorial, você executará ambos na mesma máquina. Em um sistema de produção, os Trabalhadores e o Serviço Temporal normalmente são implantados em sistemas diferentes, da mesma forma que um aplicação web de produção terá seus servidores web e servidores de banco de dados implantados em sistemas diferentes. Um sistema de produção terá vários trabalhadores, muitas vezes centenas ou milhares deles, pois isso aumenta a disponibilidade e a taxa de transferência do aplicativo.

Enquanto o Temporal orquestra o fluxo de trabalho e as atividades, o MongoDB servirá como a camada de armazenamento persistente para o aplicação, mantendo nosso gerenciamento de dados consistente e confiável. O MongoDB armazena todas as informações da conta, incluindo IDs de conta, nomes e saldos atuais. Ao manter esses dados no MongoDB, o aplicação garante que os fluxos de trabalho sempre tenham acesso a informações precisas da conta, mesmo durante interrupções inesperadas.
O MongoDB complementa o Temporal fornecendo:
- Persistência de dados confiável: o MongoDB armazena todas as informações da conta, garantindo que os fluxos de trabalho temporais sempre tenham acesso a dados atualizados, mesmo após reinicializações ou falhas.
- Escalabilidade: a capacidade do MongoDB de escalar vários servidores permite que o aplicação lide com quantidades crescentes de dados de contas e atividades de transações sem afetar o desempenho.
- Resiliência a interrupções: a replicação do MongoDB mantém os dados disponíveis durante interrupções, permitindo que os fluxos de trabalho continuem sem interrupção.
O Temporal vem com uma interface baseada na Web para visualizar e interagir com o Workflow Executions. Ele oferece visibilidade do que está sendo executado agora e do que foi executado anteriormente. A interface do usuário da Web oferece uma maneira conveniente de visualizar o histórico de eventos para cada execução do fluxo de trabalho, permitindo que você veja a entrada, o status atual e a saída do fluxo de trabalho, bem como os mesmos detalhes sobre cada atividade executada como parte desse fluxo de trabalho. Durante este tutorial, você usará essa ferramenta para confirmar e resolver uma interrupção no serviço bancário.
Execute o seguinte comando para buscar uma cópia do código usado neste tutorial:
1 git clone https://github.com/mongodb-developer/mongodb-java-money-transfer
Este repositório que você clonou tem dois subdiretórios. Cada um é um projeto separado com sua própria base de código e arquivo de configuração de compilação Maven. O
bank-services
subdiretório contém o código que gerencia as contas bancárias, oferece uma API HTTP para executar operações nessas contas e fornece uma GUI para visualizar e controlar o status de cada conta. O money-transfer-application
diretório contém o aplicação Temporal que transfere dinheiro entre contas bancárias usando um cliente HTTP para invocar operações de retirada e depósito por meio da API de serviços bancários. Este tutorial se concentra no código do aplicação de transferência de valores.No Temporal Java SDK, um fluxo de trabalho é definido usando uma interface, identificada por uma
@WorkflowInterface
anotação. Em seguida, o desenvolvedor usa a @WorkflowMethod
anotação para especificar qual dos métodos executar quando o fluxo de trabalho for executado. Neste exemplo, a interface de fluxo de trabalho especifica um método de fluxo de trabalho chamado transfer
que aceita um objeto que contém detalhes (como a conta do remetente, a conta do destinatário e o valor) da transferência.Por questões de brevidade, o código a seguir omite declarações import, declarações log, comentários de código e outros elementos que podem desviar a atenção da explicação. Você pode ignorar o
approve
método por enquanto, pois ele será abordado mais tarde no tutorial.1 package org.mongodb.workflows; 2 3 4 public interface MoneyTransferWorkflow { 5 6 7 String transfer(TransferDetails input); 8 9 10 void approve(String managerName); 11 }
A interface também tem uma classe de implementação correspondente, que substitui esses métodos, fornecendo o código a ser executado durante a execução do fluxo de trabalho.
Como essa implementação faz referência a atividades que realizam operações de retirada e depósito, é útil primeiro ver como elas são definidas. Semelhante ao fluxo de trabalho, eles são definidos usando uma interface que usa anotações para especificar quais métodos chamar:
1 package org.mongodb.activities; 2 3 4 public interface AccountActivities { 5 6 7 String withdraw(String account, int amount, String referenceId); 8 9 10 String deposit(String account, int amount, String referenceId); 11 }
Nesse caso, os
withdraw
métodos de atividade deposit
usam três argumentos: o identificador da conta, a quantidade de dinheiro a ser debitado ou crédito e um número de referência que identifica exclusivamente essa solicitação. O serviço bancário utiliza este número de referência como uma chave de idempotência, permitindo detectar e ignorar pedidos duplicados. Cada método retorna uma string, que é a ID da transação fornecida pelo serviço bancário quando a operação é bem-sucedida.Assim como na interface Workflow, a interface Activity tem uma classe de implementação correspondente, que fornece o código a ser executado quando a Activity é executada. A implementação da atividade usa um cliente HTTP para emitir uma solicitação para o serviço bancário, analisa a resposta e retorna o ID da transação. O único código específico do Temporal em qualquer método é um método utilitário,
Activity.wrap
, que converte uma exceção IOException
marcada (como ) em uma exceção não verificada. Isso simplifica o código, pois o Temporal capturará qualquer exceção que ocorra e a tratará retentando a atividade em uma tentativa subsequente.Agora que você entende como o fluxo de trabalho e as atividades são definidos, você tem o contexto necessário para entender a classe de implementação do fluxo de trabalho. O trecho mostrado abaixo omite o código relacionado às aprovações do gerente para transferências maiores, que serão abordadas mais tarde neste tutorial.
1 public class MoneyTransferWorkflowImpl implements MoneyTransferWorkflow { private final ActivityOptions options = ActivityOptions.newBuilder() 2 .setStartToCloseTimeout(Duration.ofSeconds(10)) 3 .setRetryOptions(RetryOptions.newBuilder() 4 .setInitialInterval(Duration.ofSeconds(1)) 5 .setMaximumInterval(Duration.ofSeconds(60)) 6 .setBackoffCoefficient(2.0) 7 .setDoNotRetry( 8 InsufficientFundsException.class.getName()) 9 .build()) 10 .build(); 11 12 private final AccountActivities activitiesStub = Workflow.newActivityStub(AccountActivities.class, options); 13 14 15 public String transfer(TransferDetails input) { 16 String withdrawKey = String.format("withdrawal-for-%s", input.getReferenceId()); 17 String withdrawResult = activitiesStub.withdraw(input.getSender(), input.getAmount(), withdrawKey); 18 19 String depositKey = String.format("deposit-for-%s", input.getReferenceId()); 20 String depositResult = activitiesStub.deposit(input.getRecipient(), input.getAmount(), depositKey); 21 22 String confirmation = String.format("withdrawal=%s, deposit=%s", withdrawResult, depositResult); 23 24 return confirmation; 25 } 26 }
A primeira declaração no código acima define as opções que controlam a execução da atividade. Existem duas partes para isso. A primeira define um Tempo limite de início a fim, que especifica a duração máxima (10 segundos, neste caso) que a atividade pode ser executada. Se exceder isso sem retornar um resultado ou erro, ela será expirada e tentada novamente. A segunda parte é a Política de Repetição das Atividades. Se omitido, o fluxo de trabalho usará a política padrão, mas este exemplo fornece uma política personalizada para facilitar a experimentação. Esta política declara que a Atividade deve ser repetida um segundo após a falha, dobrando o atraso entre as tentativas subsequentes, com um atraso máximo de 60 segundos. Ele também declara que não haverá novas tentativas se a atividade lançar um
InsufficientFundsException
.A próxima declaração chama o
Workflow.newActivityStub
método, passando a interface da Atividade e as opções descritas acima. Isso retorna um stub com os métodos definidos na interface Activity. Você chamará esses métodos para executar as atividades do fluxo de trabalho. O trabalhador observa essas chamadas, enviando mensagens para o serviço temporal que resultam na adição de novas tarefas à sua fila de tarefas, o que, por sua vez, resulta na atualização do histórico de eventos e em um trabalhador executando essas atividades.Por fim, o
transfer
método atende à solicitação de transferência de dinheiro withdraw
deposit
chamando as atividades e, concatenando seus IDs de transação em uma mensagem de confirmação retornada pelo fluxo de trabalho. A chamada para as withdraw
atividades e deposit
inclui uma chave de idempotência criada a partir do ID de referência. Essas chaves são repassadas na chamada para o serviço bancário, garantindo que nem o retirado nem o depósito sejam duplicados.Conforme explicado anteriormente, o Trabalhador é responsável por executar o Código do Fluxo de Trabalho e da Atividade. Ele faz isso pesquisando uma fila de tarefas mantida pelo Temporal Service, que fornece detalhes sobre qual código executar. Em seguida, ele executa o código e relata o status ao Serviço Temporal, que atualiza o Histórico de Eventos. Ele usa um objeto de cliente temporal , fornecido pelo SDK temporal, para se comunicar com o serviço temporal. A implementação Worker é fornecida pelo SDK Temporal, mas a
org.mongodb.workers.ApplicationWorker
classe neste aplicação é o que a configura e inicia:1 package org.mongodb.workers; 2 3 public class ApplicationWorker { 4 5 public static final String TASK_QUEUE_NAME = "money-transfer-task-queue"; 6 7 public static void main(String[] args) { 8 WorkflowServiceStubs serviceStub = WorkflowServiceStubs.newLocalServiceStubs(); 9 WorkflowClient client = WorkflowClient.newInstance(serviceStub); 10 WorkerFactory factory = WorkerFactory.newInstance(client); 11 Worker worker = factory.newWorker(TASK_QUEUE_NAME); 12 worker.registerWorkflowImplementationTypes(MoneyTransferWorkflowImpl.class); 13 14 AccountActivities activities = new AccountActivitiesImpl(); 15 worker.registerActivitiesImplementations(activities); 16 17 factory.start(); 18 } 19 }
A primeira instrução no
main
método abaixo retorna uma WorkflowServiceStubs
instância , que representa uma conexão com um serviço temporal local. A próxima instrução cria um cliente temporal que se comunicará com esse serviço. As duas declarações a seguir criam a Worker
instância, que pesquisará a fila de tarefas identificada pela constante definida como uma string no topo dessa classe. Como você verá em um momento, o nome da Fila de Tarefa também é referenciado em outra classe, e defini-lo como uma constante que é usada por ambos garante que o valor seja o mesmo em ambos os locais. As próximas três linhas registram os tipos de Fluxo de Trabalho e Atividade que este Trabalhador suportará. A linha final inicia o Trabalhador, fazendo com que ele comece a pesquisar a Fila de Tarefas, escolhendo Tarefas correspondentes aos tipos de Fluxo de Trabalho e Atividade compatíveis e executando o código conforme especificado na Tarefa.A Execução do Fluxo de Trabalho começa quando o Serviço Temporal recebe uma solicitação para executar um Tipo de Fluxo de Trabalho específico com dados de entrada específicos. Essa solicitação vem de um cliente temporal. A classe Starter é um pequeno programa que cria um Cliente Temporal (assim como o Trabalhador fez) e o usa para emitir uma solicitação de Execução de Fluxo de Trabalho para o Serviço Temporal, passando os dados de entrada para o Fluxo de Trabalho.
1 package org.mongodb; 2 3 public class Starter { 4 private static final Logger logger = LoggerFactory.getLogger(Starter.class); 5 6 public static void main(String[] args) { 7 // The sender, recipient, and transfer amount are parsed from 8 // command-line arguments. The parsing code here is omitted for brevity. 9 10 String referenceId = UUID.randomUUID().toString(); 11 12 TransferDetails details = new TransferDetails(sender, recipient, transferAmount, referenceId); 13 14 String workflowId = String.format("transfer-%d-%s-to-%s", transferAmount, sender, recipient).toLowerCase(); 15 16 WorkflowServiceStubs serviceStub = WorkflowServiceStubs.newLocalServiceStubs(); 17 WorkflowClient client = WorkflowClient.newInstance(serviceStub); 18 WorkflowOptions options = WorkflowOptions.newBuilder() 19 .setTaskQueue(ApplicationWorker.TASK_QUEUE_NAME) 20 .setWorkflowId(workflowId) 21 .build(); 22 23 MoneyTransferWorkflow workflow = client.newWorkflowStub(MoneyTransferWorkflow.class, options); 24 String confirmation = workflow.transfer(details); 25 26 logger.info("Money Transfer complete. Confirmation: {}", confirmation); 27 28 System.exit(0); 29 } 30 }
Quando você revisou o código da interface de fluxo de trabalho acima, você viu que o método de fluxo de trabalho usa um
TransferDetails
objeto como entrada. Contém informações sobre a conta do emissor, a conta do destinatário, o valor que está sendo transferido e um ID de referência. A ID de referência é um identificador usado pelos bancos para rastreamento e processamento de transações, e a classe Starter gera esse valor usando um UUID. Conforme explicado anteriormente, usamos isso para gerar chaves de idempotência para cada atividade, que o serviço bancário usa para detectar solicitações duplicadas.Enquanto este ID de referência é utilizado apenas pelo aplicação, o ID de Fluxo de Trabalho é um identificador usado pelo próprio Temporal. O Temporal garante que não mais do que uma Execução de Fluxo de Trabalho com um determinado ID esteja em execução em um determinado momento. Ele é especificado ao enviar uma solicitação de Execução do Fluxo de Trabalho e normalmente tem um valor significativo no aplicação subjacente. Por exemplo, um aplicação de processamento de pedido pode usar o número do pedido como ID do fluxo de trabalho, o que protegeria contra um clique acidental em um aplicação da web que envia o mesmo pedido duas vezes. Nesse caso, o aplicação constrói um ID de fluxo de trabalho com base no emissor, no destinatário e no valor. Ou seja, ele ignorará uma nova solicitação de transferência da mesma quantidade de dinheiro entre as mesmas pessoas que outra transferência já em andamento.
A
workflow.transfer
chamada, que faz referência ao método definido na interface do Workflow, instrui o Trabalhador a enviar essa solicitação ao Serviço Temporal. Esta é uma chamada síncrona, e o código bloqueará aqui até que a Execução do Fluxo de Trabalho seja concluída. Se essa execução tiver sido bem-sucedida, a chamada retornará a saída do método Workflow, que é a mensagem de confirmação que se junta aos IDs de transação para as operações de retirada e depósito. Embora este exemplo use uma chamada síncrona para o fluxo de trabalho, as APIs temporais também fornecem uma contraparte assíncrona que você pode usar se não quiser bloquear enquanto a execução do fluxo de trabalho é concluída.Agora que você entende o código, é hora de executá-lo e vê-lo em ação.
O serviço bancário usa o MongoDB para armazenar informações sobre as contas bancárias, incluindo seus saldos atuais. Ele localiza o cluster MongoDB por meio de uma variável de ambiente (
MONGO_CONNECTION_STRING
), que você deve definir antes de iniciar o serviço. O serviço bancário criará um banco de dados denominado bankingdemo
nesse cluster.Siga as instruções abaixo que correspondem ao seu ambiente e ajuste a string de conexão para corresponder à do seu cluster MongoDB .
1 export MONGO_CONNECTION_STRING="mongodb+srv://<username>:<password>@cluster.abcde.mongodb.net/"
1 set MONGO_CONNECTION_STRING="mongodb+srv://<username>:<password>@cluster.abcde.mongodb.net/"
1 env:MONGO_CONNECTION_STRING="mongodb+srv://<username>:<password>@cluster.abcde.mongodb.net/"
Depois de definir a variável de ambiente e verificar se o cluster MongoDB está em execução, altere para o
bank-services
diretório em seu terminal:1 cd bank-services
Por fim, execute o seguinte comando do Maven para compilar o código e iniciar os serviços bancários e a interface do usuário:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.banking.Main"
O
README.md
arquivo nesse projeto descreve como você pode interagir manualmente com os serviços bancários por meio de um cliente HTTP, mas você não precisará fazer isso neste tutorial.Ao executar isso pela primeira vez, você precisará adicionar pelo menos duas contas bancárias para poder transferir dinheiro entre elas.

Clique no botão Adicionar conta para fazer isso. Os exemplos a seguir usarão os nomes Ana e David para essas contas. Você pode usar o nome que quiser, mas precisará usar os nomes selecionados ao executar o fluxo de trabalho de transferência de dinheiro.
A captura de tela abaixo mostra a GUI após adicionar essas duas contas. Cada um tem um saldo inicial de $1000 (o símbolo de moeda exibido pode variar de acordo com sua localização) e está disponível para chamadas de API.

A CLI Temporal é uma ferramenta de linha de comando para interagir com um Serviço Temporal. Por exemplo, você pode usá-lo para iniciar, sinalizar ou cancelar Execuções de Fluxo de Trabalho, verificar quais estão em execução no momento e visualizar seus Históricos de eventos ou resultados. A Temporal CLI também inclui uma versão leve do Temporal Service. Não tem dependências externas e é iniciado em segundos, o que o torna uma ótima opção para o desenvolvimento local.
Como as próximas etapas exigem que o Temporal Service esteja em execução, inicie-o agora abrindo outro terminal e executando este comando:
1 temporal server start-dev --db-filename temporal-service.db
Isso inicia o Serviço Temporal, com a UI da Web escutando em sua porta padrão8233(). Se esta porta não estiver disponível, acrescente
--ui-port
a este comando, seguido pelo número da porta de sua escolha.A
--db-filename
opção especifica o local do caminho para um arquivo em que o Serviço Temporal persistirá os Históricos de Eventos e outros dados. Se o arquivo não existir, ele será criado. Se existir, como será o caso se você reiniciar o Serviço Temporal, ele o utilizará para carregar dados da sessão anterior. Se você omitir esta opção, o Temporal Service armazenará os dados na memória, portanto, eles não persistirão durante a reinicialização.O Worker se comunica com o Temporal Service, pesquisando uma Fila de Tarefa que indica o que deve ser executado para que a Execução do Fluxo de Trabalho progrida. Quando o Trabalhador aceita essas tarefas, ele executa os métodos apropriados de Fluxo de Trabalho e Atividade e, em seguida, relata o resultado ao Serviço Temporal.
Você executará todos os comandos restantes neste tutorial a partir do
money-transfer-application
diretório, então altere para esse diretório agora.1 cd money-transfer-application
Execute o comando Maven abaixo para compilar o código do aplicação e executar o
main
método na classe que configura e inicia o Worker para este projeto:1 mvn compile exec:java -Dexec.mainClass="org.mongodb.workers.ApplicationWorker"
O Temporal Service agora está em execução e o Worker que você iniciou está pesquisando a Fila de Tarefas para a transferência de dinheiro. A próxima etapa é enviar uma solicitação ao Serviço Temporal para iniciar o fluxo de trabalho de transferência de dinheiro. Em seguida, o Serviço Temporal adicionará a primeira Tarefa à fila e, posteriormente, adicionará mais Tarefas à medida que a Execução do Fluxo de Trabalho progride.
Execute o comando abaixo para executar a
org.mongodb.Starter
classe, que envia a solicitação de execução e, em seguida, exibe o resultado quando a execução é concluída. Este exemplo transfere $100 deMaria para David. Se você configurar contas com nomes diferentes, modifique o comando para usá-las.1 mvn compile exec:java -Dexec.mainClass="org.mongodb.Starter" -Dexec.args="Maria David 100"
Você deve ver algumas linhas de saída. O resultado final deve incluir o resultado retornado pelo Fluxo de trabalho, que é uma mensagem de confirmação que contém os IDs de transação das operações de retirada e depósito. Esses são gerados aleatoriamente pelo serviço bancário, portanto, serão diferentes do exemplo mostrado aqui:
1 [org.mongodb.Starter.main()] INFO org.mongodb.Starter - Money Transfer complete. Confirmation: withdrawal=W4842620791, deposit=D7932687105
Você também deve ver que os saldos mostrados na interface do usuário foram alterados. Antes de executar o fluxo de trabalho, ambos os saldos tinham o mesmo valor. Depois de realizar a transferência, a conta deMaria foi reduzida pelo valor da transferência, enquanto a de David teve um aumento correspondente.

Você também pode usar a UI do Temporal Web para ver os resultados — e muitos outros detalhes — para esta execução do fluxo de trabalho. Inicie seu navegador e navegue até http://localhost:8233/. Se você utilizou a
--ui-port
opção para especificar uma porta diferente ao iniciar o Serviço Temporal, substitua 8233 nesse URL pela porta que você especificou. Você deve ver uma página semelhante à desta captura de tela:
A página principal lista as Execuções de Fluxo de Trabalho atuais e recentes, exibindo o status, ID do Fluxo de Trabalho e horários de início e término de cada uma. Como foi o seu primeiro, é o único mostrado agora. Clique no link na coluna ID do Fluxo de Trabalho para visualizar a página de detalhes. Você deve ver algo semelhante à captura de tela abaixo, que foi anotada para rotular as quatro seções principais.

Abaixo está um trecho que mostra as duas seções perto da parte superior, com chamadas numeradas para chamar sua atenção para partes específicas. Isso mostra 1) quando a execução do fluxo de trabalho começou e terminou, 2) o tipo de fluxo de trabalho que era, 3) a fila de tarefas usada, 4) os dados fornecidos como entrada e 5) o resultado que retornou como saída.

A seção Linha do tempo que segue esta seção ilustra o tempo e a sequência relativa para cada atividade. A tabela Histórico de eventos, localizada abaixo, mostra os detalhes de cada evento que ocorreu durante a Execução do Fluxo de Trabalho. Esses detalhes não são úteis apenas para depuração, mas também permitem que o trabalhador reconstrua completamente o estado do aplicação após uma falha.
Você agora executou com sucesso o fluxo de trabalho de transferência de dinheiro. Agora é a hora de você ver o que acontece quando as coisas não saem como planejado.
Agora você fará outra transferência deMaria para David, mas desta vez simulará uma interrupção de serviço com o banco deMaria.
Clique no botão Parar para a conta de Maria na GUI. Isso desabilita o acesso à sua conta por meio do serviço bancário, simulando uma interrupção em seu banco. Como a GUI também usa esse serviço, ela mostra UNKNOWN para o saldo, pois não pode acessar a conta dela.

O Serviço Temporal e seu Trabalhador ainda devem estar em execução, então você só precisa executar novamente o Fluxo de Trabalho para realizar outra transferência. Faça isso agora executando o seguinte comando:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.Starter" -Dexec.args="Maria David 100"
Ao contrário antes, quando isso mostrava rapidamente o resultado retornado pelo fluxo de trabalho, a saída que você verá no terminal agora sugere que o fluxo de trabalho começou, mas ainda não terminou. A interface do usuário da Web do Temporal é uma ferramenta conveniente para depurar problemas como esse.
Navegue até a página de detalhes desta Execução do Fluxo de Trabalho na interface do usuário da Web. O status mostrado próximo ao canto superior esquerdo indica que ainda está em execução. A seção Linha do tempo mostra uma linha horizontal composta de linhas verticais vermelha, o que indica que a atividade correspondente (Retirada) está falhando. A aba Atividades pendentes (destacada com uma caixa laranja nesta captura de tela) tem o número 1 ao lado, o que indica que uma atividade falhou e está aguardando outra tentativa de repetição.

Clique na aba Atividades pendentes agora, que mostrará uma tela semelhante à abaixo. Isso identifica o tipo de atividade que está falhando, quantas tentativas já foram tentadas, quantas tentativas permanecem e quanto tempo levará até a próxima tentativa. A seção Última falha contém os detalhes da falha, que inclui o rastreamento da pilha. Nesse caso, a mensagem (duplicada aqui em texto grande) indica que a falha é causada por um
IllegalStateException
lançado pelo aplicação porque o banco de Ana não está disponível.
Clique na guia Histórico à esquerda da guia Atividades pendentes para retornar à página anterior para esta execução do fluxo de trabalho.
Clique no botão Iniciar para a conta de Ana na interface do usuário. Com a interrupção agora resolvida, o fluxo de trabalho será executado até a conclusão na próxima tentativa de repetição, como se nunca tivesse ocorrido uma interrupção . Você saberá que isso aconteceu quando o status da Execução do Fluxo de Trabalho mudar para Concluído na interface do usuário da Web. Você também deve observar que os saldos mostrados na interface do usuário foram alterados para refletir a transferência concluída.
Você aprendera anteriormente sobre os problemas que podem ocorrer se ocorrer uma falha entre a retirada e o depósito. O Temporal fornece a Execução Durável, o que permite que ele continue a execução como se a falha nunca tivesse acontecido. Agora, é hora de você experimentar isso por si mesmo, matar o Trabalhador após a retirada, mas antes do depósito.
Como a execução do fluxo de trabalho acontece muito rapidamente, você precisará introduzir um atraso entre as duas atividades que lhe dê tempo para matar o trabalhador.
Edite o
src/main/java/org/mongodb/workflows/MoneyTransferWorkflowImpl.java
arquivo no money-transfer-application
projeto. Entre as withdraw
deposit
atividades e no transfer
método nessa classe, você encontrará a seguinte declaração comentada:1 //Workflow.sleep(Duration.ofSeconds(30));
Para usar um método fornecido pela API Temporal Workflow para iniciar um Durable Timer, que pausa a Execução do Fluxo de Trabalho pela duração especificada de 30 segundos. Isso deve dar tempo suficiente para você matar o Worker.
Cancele o comentário desta declaração. Sempre que modificar o fluxo de trabalho ou o código de atividade, você deve reiniciar todos os trabalhadores para que eles comecem a usar a versão atualizada. Portanto, você deve localizar a janela do terminal onde iniciou o Operador, pressionar ctrl-C para eliminar esse processo e, em seguida, executar novamente o comando que você usou para iniciá-lo:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.workers.ApplicationWorker"
Você acabou de introduzir o atraso que lhe dará tempo para travar o aplicação antes que ele seja concluído. Agora é hora de executar o fluxo de trabalho novamente:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.Starter" -Dexec.args="Maria David 100"
Depois de iniciar, volte para a sessão de terminal onde você iniciou o Trabalhador. Assim que ele relatar que a retirada foi concluída, pressione ctrl-C para eliminar o Worker.
Agora você pode usar a interface do usuário da Web para encontrar essa execução do fluxo de trabalho. A linha do tempo e a tabela de histórico de eventos devem confirmar que a
withdraw
atividade foi concluída, mas a deposit
atividade não.Agora que você interrompeu o aplicação, é hora de vê-lo ser retomado de onde parou. Execute novamente o comando que você usou para iniciar seu Worker:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.workers.ApplicationWorker"
Retorne à interface do usuário da web e localize a página de detalhes desta execução do fluxo de trabalho. Você deve observar que o Fluxo de Trabalho é executado até a conclusão, embora isso possa levar mais tempo devido ao atraso causado pela instrução
Workflow.sleep
e o tempo limite que o Serviço Temporal usa para detectar falhas no Worker.Depois de concluído, observe a visualização da linha do tempo, que ilustra quando ocorreu a retirada, o sono e o depósito. Você também verá que cada um deles aconteceu apenas uma vez. Se você quiser mais provas de que a retirada não se repetiu, apesar de ter sido executada antes da falha do Worker, verifique a janela do terminal onde você iniciou os serviços bancários. As mensagens de registro mostradas confirmarão que houve apenas uma única retirada.
Depois que você eliminou o trabalhador, o progresso da execução do fluxo de trabalho cessou porque não havia trabalhadores em execução. Uma implantação de produção de um aplicação Temporal terá vários Trabalhadores. Um motivo para isso é a escalabilidade, pois cada trabalhador adicional expande a capacidade de executar mais execuções de fluxo de trabalho simultaneamente. Outro motivo é a disponibilidade, já que outros trabalhadores assumirão automaticamente o lugar do que falhou.
Se você quiser ver isso por si mesmo, inicie um segundo trabalhador e repita as etapas nesta seção, eliminando o trabalhador que
withdraw
executou sua atividade. Você deve achar que o Trabalhador restante é capaz de concluir a Execução do Fluxo de Trabalho sem qualquer intervenção de sua parte.Um fluxo de trabalho pode ter etapas automatizadas e manuais. O Temporal suporta ambos e você pode usar ambos em um único fluxo de trabalho. Esta parte do tutorial mostrará como fazer isso.
Os requisitos de negócios do aplicação de transferência de valores determinam que as transferências de valores relativamente pequenos devem ser concluídas automaticamente, como você já viu. As transferências que excederem $500 são colocadas em espera até que possam ser revisadas e aprovadas por um gerente.
Há duas partes neste processo de aprovação. A primeira está no Fluxo de Trabalho, que verifica se o valor excede o limite. Em caso afirmativo, define a variável
hasManagerApproval
como false
. Logo após a declaração que faz isso, o código chama Workflow.await
para bloquear até que essa variável tenha um true
valor. O design do Temporal permite que as Execuções de Fluxo de Trabalho aguardem de forma eficiente e confiável uma condição ou Temporizador, mesmo para durações que durem vários meses ou anos. Aqui está um trecho mostrando o código relevante (alguns comentários foram modificados ou omitidos aqui por brevidade):1 // Large transfers must be explicitly approved by a manager 2 if (input.getAmount() > 500) { 3 logger.warn("This transfer is on hold awaiting manager approval"); 4 hasManagerApproval = false; 5 } 6 7 // Workflow Execution will not progress until hasManagerApproval is true 8 Workflow.await(() -> hasManagerApproval); 9 10 // Transfer approved. Will now withdraw money from the sender's account 11 logger.info("Starting withdraw operation");
Como a variável é definida
hasManagerApproval
true
como? Essa é a segunda parte e é feita no approve
método, que foi anotado com @SignalMethod
na interface do Workflow. Um Sinal é uma mensagem nomeada que pode fornecer dados para um Fluxo de Trabalho em execução.Os sinais são enviados para o Serviço Temporal, que os passa para o Trabalhador, que por sua vez invoca o método correspondente na Definição de Fluxo de Trabalho. Neste exemplo, o nome do Sinal é
approve
e seus dados são o nome do gerente que aprova a transferência. Este método atualiza a hasManagerApproval
variável, que permite que a transferência continue porque não está mais bloqueada na Workflow.await
chamada. Aqui está o código para o approve
método:1 2 public void approve(String managerName) { 3 logger.info("This transfer has now been approved by {}", managerName); 4 hasManagerApproval = true; 5 }
Existem várias maneiras de enviar um sinal. Você pode usar a CLI Temporal ou a interface do usuário da Web, que são abordagens genéricas que não exigem a escrita de nenhum código. Você também pode usar APIs fornecidas pelo SDK Temporal, que permite enviar um sinal de seu aplicação, como faz a GUI bancária neste tutorial. Clicar no botão Aprovar uma transação solicita o ID do fluxo de trabalho e o nome do aprovador. Quando você clica no botão OK, ele chama estas três linhas de código:
1 WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); 2 WorkflowClient client = WorkflowClient.newInstance(service); 3 client.newUntypedWorkflowStub(workflowId).signal("approve", managerName);
As duas primeiras linhas de código criam um Cliente Temporal e são idênticas às que você viu no código usado para iniciar o Fluxo de Trabalho. A última linha de código utiliza este cliente e o ID de fluxo de trabalho para localizar a execução do fluxo de trabalho, enviando-o um sinal denominado
approve
com o nome do gerente como os dados de entrada.Você pode notar que este código usa
newUntypedWorkflowStub
. Embora seja possível usar um stub digitado, como o código usado para iniciar o fluxo de trabalho fez, usar um stub não digitado permite que a GUI envie o sinal genericamente. Em outras palavras, essa abordagem evita uma dependência entre a GUI e o código do aplicação de transferência de dinheiro.Agora que você entende como o processo de aprovação é implementado, é hora de você vê-lo em ação iniciando e aprovando uma transferência.
O Serviço Temporal e seu Trabalhador ainda devem estar executando de forma anterior, então você só precisa executar novamente o Fluxo de Trabalho para realizar outra transferência. Especifique um valor que requer aprovação executando o comando abaixo:
1 mvn compile exec:java -Dexec.mainClass="org.mongodb.Starter" -Dexec.args="Maria David 600"
Como essa transferência está pendente de aprovação, você não verá uma mensagem de confirmação até que ela seja concluída.
Alterne para o terminal onde você iniciou o Worker. A última linha de saída deve ser uma mensagem que diz: "This transfer is on hold awaiting manager approval."
Navegue até a página de detalhes desta Execução do Fluxo de Trabalho na interface do usuário da Web. A linha do tempo e o histórico de eventos devem ser semelhantes à seguinte captura de tela:

Observe que nem a
withdraw
atividade nem a deposit
atividade aparecem aqui. Isso ocorre porque o código do Workflow não os chama até que a transferência seja aprovada.Ao prosseguir para a próxima etapa, certifique-se de que a interface do usuário da web permaneça visível na tela (se possível) para que você possa ver o progresso da execução do fluxo de trabalho.
Na interface do usuário do Banco, clique no botão Aprovar uma transação. Isso exibe uma caixa de diálogo. Copie o ID de fluxo de trabalho da interface do usuário da Web (ele aparece na parte superior da página de detalhes) e cole-o no campo ID de fluxo de trabalho. Insira um nome no campo Nome do gerente.

Por fim, clique no botão OK para enviar o Sinal e aprovar a transferência. Em breve, você verá na interface do usuário da Web que a execução do fluxo de trabalho é concluída.

O histórico de eventos também reflete a aprovação. O ícone cor de laranja na visualização da Linha do Tempo representa o
approve
Sinal que está sendo enviado. O Evento sinalizado de execução do fluxo de trabalho na tabela de histórico fornece detalhes adicionais, incluindo o carimbo de data/hora e o nome do aprovador.
Parabéns por concluir o tutorial! Você viu como um aplicação Temporal tenta novamente uma operação que falha devido a uma interrupção de serviço , em recuperação automaticamente como se a interrupção nunca tivesse ocorrido. Você viu como a Execução Durável permite que o aplicação se recupere automaticamente do encerramento inesperado, retomando de onde parou em vez de recomeçar do início. Por fim, você viu como os aplicativos Temporais oferecem suporte a fluxos de trabalho humanos no loop, permitindo que você crie aplicativos que envolvem tarefas que exigem etapas manuais de processamento.
O código oferece muito mais oportunidades para explorar. Por exemplo, você pode experimentar alterações na política de novas tentativas no fluxo de trabalho. Você também pode implementar o padrão Saga para desfazer uma transferência em andamento se a conta do destinatário não estiver disponível. O fluxo de trabalho de transferência de dinheiro também fornece alguns testes correspondentes, incluindo um que testa o cenário humano-in-the-loop.
Outra ótima maneira de continuar aprendendo é se inscrevendo em um dos cursos de treinamento prático gratuitos da Temporal. Confira também a página da comunidade Temporal, onde você encontrará informações sobre os próximos eventos, links para documentação e amostras de código, e um convite para participar do nosso workspace Slack. Se quiser saber mais sobre o MongoDB, consulte nossos Fóruns da comunidade ou visite nosso centro de desenvolvedores , onde temos tutoriais sobre como usar o Kubernetes para microsserviços Spring Boot.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.