Comunicação entre serviços em uma configuração de microsserviços

Last reviewed 2024-06-26 UTC

Este é o terceiro de uma série de quatro partes sobre como projetar, criar e implantar microsserviços. Esta série descreve os vários elementos de uma arquitetura de microsserviços. A série inclui informações sobre os benefícios e as desvantagens do padrão de arquitetura de microsserviços e como aplicá-lo.

  1. Introdução a microsserviços
  2. Refatorar um monolítico em microsserviços
  3. Comunicação entre serviços em uma configuração de microsserviços (este documento)
  4. Rastreamento distribuído em um aplicativo de microsserviços

Esta série é destinada a desenvolvedores e arquitetos de aplicativos que projetam e implementam a migração para refatorar um aplicativo monolítico em um aplicativo de microsserviços.

Neste documento, você verá a comparação entre as mensagens assíncronas e as APIs síncronas em microsserviços. O documento orienta você na desconstrução de um aplicativo monolítico e mostra como converter uma solicitação síncrona no aplicativo original em fluxo assíncrono na nova configuração baseada em microsserviços. Essa conversão inclui a implementação de transações distribuídas entre serviços.

Exemplo de aplicativo

Neste documento, você usa um aplicativo de comércio eletrônico pré-criado chamado Online Boutique. O aplicativo implementa fluxos de comércio eletrônico básicos, como navegação, adição de produtos ao carrinho e finalização da compra. O aplicativo também apresenta recomendações e anúncios com base na seleção do usuário.

Separação lógica de serviço

Neste documento, você isola o serviço de pagamento do restante do aplicativo. Todos os fluxos no aplicativo original Online Boutique são síncronos. No aplicativo refatorado, o processo de pagamento é convertido em um fluxo assíncrono. Portanto, ao receber um pedido de aprovação de compra, em vez de processá-la imediatamente, você envia uma confirmação "solicitação recebida" ao usuário. Em segundo plano, uma solicitação assíncrona é acionada para o serviço de pagamento para processar o pagamento.

Antes de mover os dados de pagamento e a lógica para um novo serviço, isole os dados de pagamento e a lógica do aplicativo monolítico. Quando você isola os dados e a lógica de pagamento no monolítico, é mais fácil refatorar o código na mesma base de código se os limites do serviço de pagamento estiverem errados (lógica ou dados comerciais).

Os componentes do aplicativo monolítico neste documento já estão modularizados, portanto, estão isolados uns dos outros. Se seu aplicativo tiver interdependências mais rigorosas, será necessário isolar a lógica de negócios e criar classes e módulos separados. Você também precisa desacoplar todas as dependências do banco de dados nas próprias tabelas e criar classes de repositório separadas. Quando você desacopla dependências de banco de dados, pode haver relações de chave estrangeira entre as tabelas divididas. No entanto, depois de separar completamente o serviço do aplicativo monolítico, essas dependências deixarão de existir e o serviço interagirá exclusivamente por meio de contratos de API ou RPC predefinidos.

Transações distribuídas e falhas parciais

Depois de isolar o serviço e dividi-lo do monolítico, uma transação local no sistema monolítico original é distribuída entre vários serviços. Na implementação monolítica, o processo de finalização da compra seguiu a sequência mostrada no diagrama a seguir.

A sequência de finalização da compra é distribuída entre vários serviços.

Figura 1. Uma sequência de processo de finalização de compra em uma implementação monolítica.

Na figura 1, quando o aplicativo recebe uma ordem de compra, o controlador de finalização de compra chama o serviço de pagamento e o serviço de pedido para processar o pagamento e salvar a ordem, respectivamente. Se alguma etapa falhar, a transação do banco de dados poderá ser revertida. Considere um cenário de exemplo em que a solicitação do pedido é armazenada com sucesso na tabela do pedido, mas o pagamento falha. Nesse cenário, toda a transação é revertida e a entrada é removida da tabela de pedidos.

Depois de separar o pagamento no próprio serviço, o fluxo de finalização de compra modificado é semelhante ao diagrama a seguir:

O processo de finalização da compra abrange vários serviços e bancos de dados.

Figura 2. Uma sequência de processo de finalização da compra depois que o pagamento é desacoplado no próprio serviço.

Na Figura 2, a transação abrange vários serviços e bancos de dados, portanto, é uma transação distribuída. Ao receber uma solicitação de pedido, o controlador de finalização da compra salva os detalhes do pedido no banco de dados local e chama outros serviços para concluir o pedido. Esses serviços, como o pagamento, podem usar o próprio banco de dados local para armazenar detalhes sobre o pedido.

No aplicativo monolítico, o sistema de banco de dados garante que as transações locais sejam atômicas. No entanto, por padrão, o sistema baseado em microsserviços que tem um banco de dados separado para cada serviço não tem um coordenador de transações global que abrange os diferentes bancos de dados. Como as transações não são coordenadas centralmente, uma falha no processamento de um pagamento não reverte as alterações confirmadas no serviço do pedido. Portanto, o sistema está em um estado inconsistente.

Os padrões a seguir são normalmente usados para lidar com transações distribuídas:

  • Protocolo de confirmação em duas fases (2PC): parte de uma família de protocolos de consenso, o 2PC coordena a confirmação de uma transação distribuída e mantém garantias de atomicidade, consistência, isolamento, durabilidade (ACID). O protocolo é dividido nas fases prepare e commit. Uma transação só será confirmada se todos os participantes votarem nela. Se os participantes não chegarem a um consenso, toda a transação será revertida.
  • Saga: o padrão Saga consiste em executar transações locais em cada microsserviço que compõe a transação distribuída. Um evento é acionado ao final de cada operação bem-sucedida ou com falha. Todos os serviços envolvidos na transação distribuída assinam esses eventos. Se os microsserviços a seguir receberem um evento de sucesso, eles executarão a operação. Se houver uma falha, os microsserviços anteriores concluirão as ações de compensação para desfazer as alterações. A Saga oferece uma visão consistente do sistema, garantindo que, quando todas as etapas forem concluídas, todas as operações sejam bem-sucedidas ou ações de compensação desde todo o trabalho.

Recomendamos o Saga para transações de longa duração. Em um aplicativo baseado em microsserviços, você espera chamadas entre serviços e comunicação com sistemas de terceiros. Portanto, é melhor projetar para consistência posterior: tente novamente por erros recuperáveis e exponha eventos de compensação que, por fim, corrigem erros irrecuperáveis.

Há várias maneiras de implementar uma saga. Por exemplo, usam mecanismos de tarefa e fluxo de trabalho, como Apache Airflow (em inglês) ou Apache Camel (em inglês). Também é possível gravar seus próprios manipuladores de eventos usando sistemas baseados no Kafka, no RabbitMQ ou no ActiveMQ.

O aplicativo Online Boutique usa o serviço de finalização da compra para orquestrar os serviços de pagamento, envio e notificação por e-mail. O serviço de finalização da compra também lida com o fluxo de negócios e pedidos. Em vez de criar seu próprio mecanismo de fluxo de trabalho, é possível usar componentes de terceiros, como o Zeebe. O Zeebe fornece um modelador baseado em interface. Recomendamos que você avalie cuidadosamente as opções do orquestrador de microsserviços com base nos requisitos do seu aplicativo. Essa escolha é uma parte essencial da execução e do escalonamento dos microsserviços.

Aplicativo refatorado

Para ativar transações distribuídas no aplicativo refatorado, o serviço de finalização da compra processa a comunicação entre os serviços de pagamento, envio e e-mail. O fluxo de trabalho genérico de modelo e notação de processos de negócios (BPMN, na sigla em inglês) usa o seguinte fluxo:

Transações em um aplicativo refatorado seguem um fluxo de trabalho do BPMN.

Figura 3. Um fluxo de trabalho de pedidos que ajuda a garantir transações distribuídas em microsserviços típicos.

O diagrama anterior mostra o seguinte fluxo de trabalho:

  • O serviço de front-end recebe uma solicitação de pedido e faz o seguinte:
    • Envia os itens do pedido para o serviço de carrinho. Em seguida, o serviço de carrinho salva os detalhes do pedido (Redis).
    • Redireciona para a página de finalização da compra. O serviço de finalização da compra extrai os pedidos do serviço de carrinho, define o status do pedido como Pending e solicita o pagamento do cliente.
    • Confirma o pagamento do usuário. Após a confirmação, o serviço de finalização da compra solicita que o serviço de e-mail gere um e-mail de confirmação e o envie ao cliente.
  • Mais tarde, o serviço de pagamento processará a solicitação.
    • Se a solicitação de pagamento for bem-sucedida, o serviço de pagamento atualizará o status do pedido para Complete.
    • Se a solicitação de pagamento falhar, o serviço de pagamento iniciará uma transação de compensação.
      • A solicitação de pagamento foi cancelada.
      • O serviço de finalização da compra altera o status do pedido para Failed.
    • Quando o serviço de pagamento fica indisponível, a solicitação expira após N segundos e o serviço de pagamento inicia uma transação de compensação.
    • O serviço de finalização da compra altera o status do pedido para Failed.

Objetivos

  • Implante o aplicativo monolítico do Online Boutique no Google Kubernetes Engine (GKE).
  • Valide o processo de finalização da compra monolítico.
  • Implante a versão de microsserviços do aplicativo monolítico refatorado
  • Verifique se o novo fluxo de finalização de compra funciona.
  • Verifique se as ações de transação e compensação distribuídas funcionam se houver uma falha.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Ao concluir este documento, você evitará o faturamento contínuo excluindo os recursos criados. Para mais informações, consulte Limpeza.

Antes de começar

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  2. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  3. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  4. Ative as APIs do Compute Engine, Google Kubernetes Engine, Cloud SQL, Artifact Analysis e Container Registry:

     gcloud services enable \
         compute.googleapis.com \
         sql-component.googleapis.com \
         servicenetworking.googleapis.com\
         container.googleapis.com \
         containeranalysis.googleapis.com \
         containerregistry.googleapis.com \
         sqladmin.googleapis.com
    
  5. Exporte as seguintes variáveis de ambiente:

    export PROJECT=$(gcloud config get-value project)
    export CLUSTER=$PROJECT-gke
    export REGION="us-central1"
    

Implantar o monolítico do e-commerce

Nesta seção, você implantará o aplicativo monolítico Online Boutique em um cluster do GKE. O aplicativo usa o Cloud SQL como banco de dados relacional. O diagrama a seguir ilustra a arquitetura do aplicativo monolítico:

Um aplicativo usa uma arquitetura monolítica.

Figura 4. Um cliente se conecta ao aplicativo em um cluster do GKE, e o aplicativo se conecta a um banco de dados do Cloud SQL.

Para implantar o app, execute as seguintes etapas:

  1. Clone o repositório do GitHub:

    git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
    
  2. Substitua o marcador PROJECT_ID no arquivo de manifesto de variáveis do Terraform:

    cd monolith-to-microservices-example/setup && \
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
    
  3. Execute os scripts do Terraform para concluir a configuração da infraestrutura e implantá-la. Para saber mais sobre o Terraform, consulte Primeiros passos com o Terraform no Google Cloud

    terraform init && terraform apply -auto-approve
    

    O script do Terraform cria o seguinte

    • Uma rede VPC chamada PROJECT_ID-vpc
    • Cluster do GKE chamado PROJECT_ID-gke
    • Uma instância do Cloud SQL chamada PROJECT_ID-mysql
      • Um banco de dados chamado ecommerce que o aplicativo usa
      • Um usuário root com a senha definida como password

    É possível modificar o script do Terraform para gerar uma senha automaticamente. Esta configuração usa um exemplo simplificado que você não deve usar na produção.

    O provisionamento de infraestrutura pode levar até 10 minutos. Quando o script for bem-sucedido, a saída terá a seguinte aparência:

    ...
    
    Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    kubernetes_cluster_name = PROJECT_ID-gke
    sql_database_name = PROJECT_ID-mysql
    vpc_name = PROJECT_ID-vpc
    
  4. Conecte-se ao cluster e crie um namespace chamado monolith. Você implanta o aplicativo em um namespace próprio no cluster do GKE:

    gcloud container clusters get-credentials $CLUSTER \
       --region $REGION \
       --project $PROJECT && \
       kubectl create ns monolith
    
  5. O aplicativo em execução no GKE usa secrets do Kubernetes para acessar o banco de dados do Cloud SQL. Crie um secret que use as credenciais de usuário para o banco de dados:

    kubectl create secret generic dbsecret \
      --from-literal=username=root \
      --from-literal=password=password -n monolith
    
  6. Crie a imagem monolítica e faça upload dela para o Container Registry:

    cd ~/monolith
    gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
    
  7. Atualize a referência no arquivo deploy.yaml para a imagem do Docker recém-criada:

    cd ~/monolith
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
    
  8. Substitua marcadores nos arquivos de manifesto de implantação e implante o aplicativo:

    cd .. && \
    DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}')
    
    sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml
    kubectl apply -f monolith/deploy.yaml
    
  9. Verifique o status da implantação:

    kubectl rollout status deployment/ecomm -n monolith
    

    O resultado será semelhante ao mostrado a seguir.

    Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available...
    deployment "ecomm" successfully rolled out
    
  10. Consiga o endereço IP do aplicativo implantado:

    kubectl get svc ecomm -n monolith \
            -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
    

    Aguarde o endereço IP do balanceador de carga ser publicado. Para sair do comando, pressione Ctrl+C. Anote o endereço IP do balanceador de carga e acesse o aplicativo no URL http://IP_ADDRESS. Pode levar algum tempo para o balanceador de carga se tornar íntegro e começar a transmitir tráfego.

Validar o fluxo de finalização de compra do monolítico

Nesta seção, você cria um pedido de teste para validar o fluxo de finalização de compra.

  1. Acesse o URL que você anotou na seção anterior, http://IP_ADDRESS.
  2. Na página inicial do aplicativo exibida, selecione qualquer produto e clique em Adicionar ao carrinho.
  3. Para criar uma compra de teste, clique em Fazer seu pedido:
  4. Quando a finalização é bem-sucedida, a janela de confirmação do pedido é exibida e exibe um ID de confirmação do pedido.
  5. Para ver os detalhes do pedido, conecte-se ao banco de dados:

    gcloud sql connect $PROJECT-mysql --user=root
    

    Também é possível usar qualquer outro método compatível para se conectar ao banco de dados. Quando solicitado, digite a senha como password.

  6. Para visualizar os detalhes do pedido salvos, execute o seguinte comando:

    select cart_id from ecommerce.cart;
    
  7. A saída será assim:

    +--------------------------------------+
    | cart_id                              |
    +--------------------------------------+
    | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 |
    +--------------------------------------+
    

Implantar o aplicativo de e-commerce baseado em microsserviços

Nesta seção, você implantará o aplicativo refatorado. Este documento aborda apenas a separação dos serviços de front-end e pagamento. No próximo documento desta série, Rastreamento distribuído em um aplicativo de microsserviços, descrevemos outros serviços, como serviços de recomendação e anúncios, que podem ser dissociados do monolítico O serviço de finalização da compra processa as transações distribuídas entre o front-end e os serviços de pagamento e é implantado como um serviço do Kubernetes no cluster do GKE, conforme mostrado no diagrama a seguir:

Os serviços de front-end e pagamento são separados do monolítico.

Figura 5. O serviço de finalização da compra organiza transações entre os serviços de carrinho, pagamento e e-mail.

Implantar os microsserviços

Nesta seção, você usará a infraestrutura provisionada anteriormente para implantar microsserviços no próprio namespace microservice:

  1. Verifique se você tem os seguintes recursos:

    • Projeto do Google Cloud
    • Ambiente shell com gcloud, git e kubectl
  2. No Cloud Shell, clone o repositório de microsserviços:

    git clone https://github.com/GoogleCloudPlatform/microservices-demo
    cd microservices-demo/
    
  3. Defina a região e o projeto do Google Cloud e verifique se a API GKE está ativada:

    export PROJECT_ID=PROJECT_ID
    export REGION=us-central1
    gcloud services enable container.googleapis.com \
    --project=${PROJECT_ID}
    

    Substitua PROJECT_ID pelo ID do seu projeto do Google Cloud.

  4. Crie um cluster do GKE e consiga as credenciais dele:

    gcloud container clusters create-auto online-boutique \
        --project=${PROJECT_ID} --region=${REGION}
    

    A criação do cluster pode levar alguns minutos.

  5. Implante microsserviços no cluster:

    kubectl apply -f ./release/kubernetes-manifests.yaml
    
  6. Aguarde até que os pods estejam prontos:

    kubectl get pods
    

    Após alguns minutos, os pods serão exibidos com o estado Running.

  7. Acesse o front-end da Web em um navegador usando o endereço IP externo do front-end:

    kubectl get service frontend-external | awk '{print $4}'
    

    Acesse http://EXTERNAL_IP em um navegador da Web para acessar sua instância do Online Boutique.

Validar o novo fluxo de finalização de compra

  1. Para verificar o fluxo do processo de finalização de compra, selecione um produto e faça um pedido, conforme descrito na seção anterior Validar o fluxo de finalização de compra monolítico.
  2. Quando você finaliza a finalização da compra, a janela de confirmação não exibe um código de confirmação. Em vez disso, você receberá detalhes do seu e-mail na janela de confirmação.
  3. Para verificar se o pedido foi recebido, se o serviço de pagamento processou o pagamento e se os detalhes do pedido foram atualizados, execute o seguinte comando:

    kubectl logs -f deploy/checkoutservice --tail=100
    

    A saída será assim:

    [...]
    {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"}
    {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"}
    {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
    

    Para sair dos registros, pressione Ctrl+C.

  4. Verifique se o pagamento foi bem-sucedido:

    kubectl logs -f deploy/paymentservice -n --tail=100
    

    A saída será assim:

    [...]
    {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454     Amount: CAD119.30128260"}
    {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
    

    Para sair dos registros, pressione Ctrl+C.

  5. Verifique se o e-mail de confirmação do pedido foi enviado:

    kubectl logs -f deploy/emailservice -n --tail=100
    

    A saída será assim:

    [...]
    {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
    

    As mensagens de registro de cada microsserviço indicam que a transação distribuída nos serviços de finalização da compra, pagamento e e-mail foi concluída com êxito.

Validar ação de compensação em uma transação distribuída

Nesta seção, simulamos um cenário em que um cliente está fazendo um pedido e o serviço de pagamento cai.

  1. Para simular a indisponibilidade do serviço, exclua a implantação e o serviço de pagamento:

    kubectl delete deploy paymentservice && \
    kubectl delete svc paymentservice
    
  2. Acesse o aplicativo novamente e conclua o fluxo de finalização da compra. Neste exemplo, quando o serviço de pagamento não responde, a solicitação expira e uma ação de compensação é acionada.

  3. No front-end da interface, clique no botão Fazer pedido. A saída será assim:

    HTTP Status: 500 Internal Server Error
    rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host"
    failed to complete the order
    main.(*frontendServer).placeOrderHandler
        /src/handlers.go:360
    
  4. Analise os registros do serviço de front-end:

    kubectl logs -f deploy/frontend --tail=100
    

    A saída será assim:

    [...]
    {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
    
  5. Analise os registros do serviço de finalização da compra:

    kubectl logs -f deploy/frontend --tail=100
    

    A saída será assim:

    [...]
    {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"}
    {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
    

    Observe que não há chamada subsequente ao serviço de e-mail para enviar notificações. Não há registro de transações, como payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca).

  6. Analise os registros do serviço de e-mail:

    kubectl logs -f deploy/emailservice --tail=100
    

    Observe que não há entradas de registro criadas para a transação com falha no serviço de e-mail.

Como orquestrador, se uma chamada de serviço falhar, o serviço de finalização da compra retornará um status de erro e sairá do respectivo processo.

Limpar

Se você planeja concluir as etapas no próximo documento desta série, Rastreamento distribuído em um aplicativo de microsserviços, é possível reutilizar o projeto e os recursos em vez de excluí-los.

Exclua o projeto

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Excluir os recursos

Se você quiser manter o projeto do Google Cloud usado neste documento, exclua os recursos individuais.

  1. No Cloud Shell, execute este comando:

    cd setup && terraform destroy -auto-approve
    
  2. Para excluir o cluster de microsserviços usando a CLI do Google Cloud, execute o seguinte comando:

    gcloud container clusters delete online-boutique \
        --location $REGION
    

A seguir