Alura > Cursos de Back-end > Cursos de Arquitetura de Software > Conteúdos de Arquitetura de Software > Primeiras aulas do curso Sistemas distribuídos: integrações resilientes e orientadas a eventos

Sistemas distribuídos: integrações resilientes e orientadas a eventos

Arquiteturas distribuídas e integrações - Introdução

Apresentando o módulo e o percurso

Olá! Como estão? Sejam muito bem-vindes a este módulo. Hoje, vamos falar sobre um tema que está no centro de praticamente todos os sistemas modernos: arquiteturas distribuídas e integrações de sistemas. Vamos construir sistemas que se comunicam, escalam e sobrevivem a falhas. Isso não é poesia; é o cotidiano de quem trabalha com software em produção. Ao longo deste percurso, vamos abordar temas como sistemas distribuídos, integrações, escalabilidade e resiliência. Vamos começar?

Antes de entrarmos no conteúdo, vou me apresentar rapidamente. Eu sou Fernando Silva, arquiteto principal de soluções na Gatinet, atuando com soluções orientadas por dados. Publiquei alguns artigos na revista Net Magazine, sou conferencista no TDC, professor em pós-graduação em Engenharia e Arquitetura de Software e tenho mais de 20 anos de experiência. Durante este módulo, estaremos juntos neste percurso.

Antes de mergulharmos, vamos apresentar uma visão holística do que vamos construir e aprender hoje. Este não é um tema isolado. Trata-se de uma progressão na qual cada tópico é a continuação do anterior.

Explorando comunicação e eventos

Vamos começar pela base. Comunicação síncrona e comunicação assíncrona parecem temas simples, mas é aqui que residem grande parte das decisões arquiteturais que raramente questionamos e que deveríamos questionar. Essa escolha pode determinar o nível de acoplamento, a resiliência e o desempenho do sistema como um todo.

Após explorarmos comunicação assíncrona, vamos analisar eventos, mensageria e arquitetura orientada a eventos, identificando quando os serviços deixam de se chamar diretamente e passam a reagir a fatos que ocorrem entre eles.

Aqui vamos abordar o barramento de eventos, como Kafka, além de produtores e consumidores. Este é o coração dos sistemas modernos em larga escala.

Tratando consistência e idempotência

Ao distribuir tudo, acabamos abrindo mão de algo; é nesse ponto que entra a consistência eventual. Precisamos entender que os dados podem ficar consistentes em algum momento, passando a trocar disponibilidade por escalabilidade, e como o negócio precisa estar ciente disso.

Em seguida, trataremos de um tema que parece um detalhe técnico, mas quebra muitos sistemas em produção: idempotência e ordenação, mensagens duplicadas e garantias. A questão não é apenas conhecer esses conceitos, mas saber como lidar com eles.

Integrando sistemas e construindo resiliência

Com isso, avançamos para a integração entre sistemas, o problema real de empresas que possuem dezenas de sistemas legados e modernos que precisam se comunicar entre si.

Então chegamos às falhas distribuídas, a parte que ninguém quer considerar, mas que é a única certeza em produção. A rede vai falhar, um serviço pode cair, e a questão é quando isso ocorrerá e como o sistema sobreviverá a essas falhas.

Fechamos o ciclo discutindo a arquitetura orientada à integração, onde tudo isso se transforma em um projeto consciente: não a integração como remendo, mas como parte do desenho desde o início.

Cada aula prepara o terreno para o tema seguinte e, ao final, teremos um sistema robusto, escalável, resiliente e protegido contra falhas.

Arquiteturas distribuídas e integrações - O que são sistemas distribuídos

Definindo sistemas distribuídos

Vamos começar definindo o que são sistemas distribuídos.

Nós gostamos muito da definição de Tanenbaum, segundo a qual um sistema distribuído é um conjunto de computadores independentes que se apresenta à pessoa usuária como um sistema único e coerente.

Explicando a transparência ao usuário e o desafio da arquitetura

Em outras palavras, quando a pessoa usuária acessa uma aplicação, uma API (Interface de Programação de Aplicações), um serviço, não sabe que, por trás, existem 10, 20, 30 serviços em comunicação, e nem precisa saber disso. O desafio recai sobre nós, nas áreas de arquitetura e desenvolvimento.

Assim, identificamos três características centrais.

Detalhando características e desafios da computação distribuída

Podemos ter múltiplos processos e serviços em execução em várias máquinas totalmente diferentes, com comunicação pela rede. Tudo passa pela rede, e a rede pode falhar. Inclusive, existem as oito falácias da computação distribuída, sobre as quais nós vamos falar mais adiante.

Nesse cenário, não há memória compartilhada, não há estado global, nem um ponto único de acesso: cada nó, cada máquina de computação opera em seu próprio contexto.

Para completar, há a concorrência: vários processos ocorrendo ao mesmo tempo, sem coordenação central. À primeira vista isso parece atraente, mas traz uma complexidade muito grande. É exatamente por isso que precisamos conhecer alguns padrões.

Arquiteturas distribuídas e integrações - Comunicação Síncrona e Assíncrona

Apresentando a comunicação síncrona e assíncrona

Aula 1 — Comunicação síncrona e assíncrona

Como os serviços se comunicam entre si? Esta é uma decisão muito importante de arquitetura. Frequentemente tomamos essa decisão no automático, sem considerar as consequências. Parece simples: um serviço precisa chamar outro, então faz a chamada. Mas como essa chamada é feita? E o que acontece se o serviço estiver indisponível ou demorar demais para responder? É isso que vamos entender ao longo deste percurso sobre comunicações.

Definindo comunicação síncrona e assíncrona

Quando um serviço precisa de informações de outro serviço ou processo, há duas opções fundamentais, e essa decisão vai muito além do que parece à primeira vista. A primeira é a comunicação síncrona: esperar a resposta. O serviço A chama o serviço B e bloqueia o fluxo de execução até que o outro serviço responda. É simples e direto: fazemos a pergunta e esperamos a resposta.

A segunda opção é enviar e continuar; é o que chamamos de comunicações assíncronas. O serviço envia uma mensagem a um intermediador, uma API (Interface de Programação de Aplicações), um broker (intermediador), um barramento de eventos e segue seu fluxo. A resposta chegará em algum momento — ou pode nem chegar —, e o sistema precisa saber lidar com isso. Essa parece uma escolha apenas técnica, mas não é só isso. Essa decisão determina três aspectos que estão no coração de qualquer arquitetura: desempenho, acoplamento e resiliência.

Definir corretamente o tipo de comunicação é extremamente importante. Definir mal é problemático. Criar uma comunicação síncrona onde deveria ser assíncrona pode impedir a escalabilidade; uma falha em cadeia pode derrubar tudo. O inverso também é verdadeiro: fazer uma comunicação assíncrona onde deveria ser síncrona adiciona complexidade desnecessária e pode comprometer a consistência de que o negócio tanto precisa.

Detalhando a comunicação síncrona

Vamos detalhar a comunicação síncrona. É a que a maioria de nós aprendeu primeiro: o serviço A chama o serviço B e fica esperando a resposta. Exemplos clássicos incluem chamadas HTTP, uso de RPC, GraphQL ou SOAP. Entre as vantagens, temos implementação simples, resposta imediata, facilidade de depuração e uma trilha de rastreamento linear. Entre as desvantagens, há o acoplamento temporal: se o serviço B estiver fora do ar, o serviço A não funciona corretamente. Podemos ter falhas em cascata: uma lentidão no serviço B ocasiona lentidão no serviço A e, com mais serviços encadeados, ocorre uma cascata de falhas. Também há aumento de latência: cada microsserviço pode ter uma latência pequena, mas a soma de todas pode resultar em uma latência muito elevada.

Não estamos afirmando que a comunicação síncrona seja ruim; ela tem seu lugar, mas exige que compreendamos seus custos. Um exemplo é a consulta de saldo bancário. Pensemos: ao abrir o aplicativo do banco e clicar em “ver saldos”, o aplicativo chama um serviço (uma API), esse serviço consulta a base de dados, e observamos o carregamento sabendo que algo está acontecendo enquanto esperamos a resposta. Esse é o processo: ficamos bloqueados, e o aplicativo não faz mais nada até obter a resposta. Isso faz sentido, pois precisamos do número naquele momento; precisamos conhecer o saldo naquele instante. Não há como mostrar um saldo provável ou uma resposta que chegará depois; precisamos fazer a solicitação, receber a resposta e aguardar até que o saldo esteja disponível na tela.

Explorando a comunicação assíncrona

Agora, sobre comunicações assíncronas: aqui o serviço A envia uma mensagem; pode enviá-la ao serviço B ou a um intermediador/barramento de eventos, como Apache Kafka, RabbitMQ, Amazon SQS ou Azure Service Bus, e segue em frente. O serviço B consome essa mensagem no seu próprio ritmo. A grande vantagem é o desacoplamento real: A e B não precisam estar ativos ao mesmo tempo; se o serviço B cair, as mensagens ficam no barramento de eventos aguardando. Outras vantagens incluem alta resiliência e escalabilidade horizontal: podemos adicionar mais consumidores para ler essas mensagens.

Tudo, porém, tem seu preço. A comunicação assíncrona traz maior complexidade, pois introduz componentes adicionais, como um broker (intermediador), consumidores e filas. Também exige a implementação de padrões de novas tentativas e de backoff (recuo), além de uma boa observabilidade.

Há outro custo: a consistência eventual; os dados ficarão temporariamente desatualizados, portanto, nossa arquitetura e nosso processo de negócio devem permitir isso; além disso, depurar torna-se mais difícil.

Um exemplo assíncrono: um pedido no iFood. Quando fazemos um pedido, aparece a tela “pedido confirmado” e pronto; o aplicativo já mostra a confirmação, mas talvez o restaurante ainda não tenha aceitado, a pessoa entregadora ainda não tenha sido notificada e o sistema de pagamentos ainda esteja processando em segundo plano. Tudo isso ocorre de forma assíncrona, em paralelo, enquanto muitas vezes já fechamos o aplicativo e vamos fazer outras coisas. Imagine se o iFood esperasse a confirmação do restaurante para exibir a tela de confirmação em uma sexta-feira à noite; como o restaurante está cheio, essa resposta pode levar 30 segundos, 1 minuto, e ficaríamos apenas olhando a tela. O sistema não escalaria nesse formato.

Equilibrando escolhas técnicas e de negócio

Esses exemplos são importantes porque mostram que uma escolha técnica dependerá muito da decisão de negócio. Precisamos fazer um equilíbrio entre comunicações síncronas e assíncronas:

Regra prática: usar a comunicação síncrona quando precisamos de resposta agora, imediatamente; e usar a assíncrona quando podemos processar depois e quando precisamos escalar. Em teoria é simples; na prática, veremos nuances entre esses dois processos.

Consolidando quando usar cada uma

Quando usar cada uma? Vamos consolidar com exemplos do mundo real.

Expondo custos ocultos

Quais são os custos ocultos?

Não existe almoço grátis: cada escolha tem seu custo. Uma boa pessoa arquiteta não é a que escolhe “a melhor” opção em abstrato, mas a que considera claramente o custo do que está escolhendo e, consequentemente, entrega valor para o negócio.

Sobre o curso Sistemas distribuídos: integrações resilientes e orientadas a eventos

O curso Sistemas distribuídos: integrações resilientes e orientadas a eventos possui 165 minutos de vídeos, em um total de 41 atividades. Gostou? Conheça nossos outros cursos de Arquitetura de Software em Back-end, ou leia nossos artigos de Back-end.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Arquitetura de Software acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas