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.
- Introdução a microsserviços
- Refatorar um monolítico em microsserviços
- Comunicação entre serviços em uma configuração de microsserviços (este documento)
- 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.
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:
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:
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
.
- Se a solicitação de pagamento for bem-sucedida, o serviço de pagamento atualizará o status do pedido para
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.
Ao concluir este documento, você evitará o faturamento contínuo excluindo os recursos criados. Para mais informações, consulte Limpeza.
Antes de começar
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
In the Google Cloud console, 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.
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
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:
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:
Clone o repositório do GitHub:
git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
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
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 comopassword
- Um banco de dados chamado
É 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
- Uma rede VPC chamada
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
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
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
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
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
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
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 URLhttp://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.
- Acesse o URL que você anotou na seção anterior,
http://IP_ADDRESS
. - Na página inicial do aplicativo exibida, selecione qualquer produto e clique em Adicionar ao carrinho.
- Para criar uma compra de teste, clique em Fazer seu pedido:
- Quando a finalização é bem-sucedida, a janela de confirmação do pedido é exibida e exibe um ID de confirmação do pedido.
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
.Para visualizar os detalhes do pedido salvos, execute o seguinte comando:
select cart_id from ecommerce.cart;
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:
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
:
Verifique se você tem os seguintes recursos:
- Projeto do Google Cloud
- Ambiente shell com
gcloud
,git
ekubectl
No Cloud Shell, clone o repositório de microsserviços:
git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/
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.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.
Implante microsserviços no cluster:
kubectl apply -f ./release/kubernetes-manifests.yaml
Aguarde até que os pods estejam prontos:
kubectl get pods
Após alguns minutos, os pods serão exibidos com o estado
Running
.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
- 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.
- 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.
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
.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
.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.
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
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.
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
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"}
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)
.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
- In the Google Cloud console, go to the Manage resources page.
- In the project list, select the project that you want to delete, and then click Delete.
- 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.
No Cloud Shell, execute este comando:
cd setup && terraform destroy -auto-approve
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
- Saiba mais sobre a arquitetura de microsserviços.
- Leia o primeiro documento desta série para saber mais sobre microsserviços, benefícios, desafios e casos de uso.
- Leia o segundo documento desta série para saber mais sobre as estratégias de refatoração de aplicativos para decompor microsserviços.
- Leia o documento final desta série para conhecer o rastreamento distribuído de solicitações entre microsserviços.