Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Padrões de Integração em Sistemas Distribuídos

Padrões de Integração em Sistemas Distribuídos

Fundamentos de Comunicação entre Serviços - Introdução

Apresentando o curso de Arquiteto de Software

Olá! Seja bem-vindo ao curso de Arquiteto de Software da Alura. Meu nome é Felipe Cabrini.

Audiodescrição: Felipe é um homem branco, com cabelos pretos e olhos castanhos. Ele está vestindo uma camiseta preta.

Explorando conteúdos avançados de comunicação e segurança

Neste curso de Arquiteto de Software, abordaremos conteúdos mais avançados. Vamos explorar padrões de comunicações síncronas, como REST, GRPC e GraphQL. Também estudaremos padrões de comunicações assíncronas utilizando ferramentas como Kafka e RabbitMQ. Discutiremos tópicos sobre modelagem de ameaça e segurança, que são fundamentais na Arquitetura de Software.

Além disso, veremos como o Arquiteto de Software contribui para os pilares da observabilidade e como criar uma cultura de observabilidade nas equipes.

Explorando padrões avançados de Arquitetura de Software

Iremos explorar alguns padrões avançados de Arquitetura de Software, como Event Sourcing (Fonte de Eventos) e Saga Pattern (Padrão Saga), entre outros. Se você é uma pessoa arquiteta de software e já trabalha na área, ou é uma pessoa desenvolvedora em Java que está aprendendo sobre Arquitetura de Software, este curso é ideal para se aprofundar nesses temas e explorar tópicos avançados de Arquitetura de Software.

Incentivando a interação e o uso dos recursos da Alura

Para extrair o máximo deste curso, sugerimos utilizar todos os recursos da Alura. Interaja bastante no fórum, poste suas dúvidas, faça comentários e tente ajudar outras pessoas. Procure realizar todas as atividades ao máximo possível. Isso certamente ajudará a fixar bem os conteúdos que abordaremos neste curso. Vamos lá?

Fundamentos de Comunicação entre Serviços - Comunicação síncrona e assíncrona entre serviços

Introduzindo o tema da aula

Olá, sejam bem-vindos à aula de hoje. Vamos discutir sobre comunicação síncrona e assíncrona entre serviços e micro-serviços.

Para começarmos, vamos imaginar um cenário no qual fomos contratados como arquitetos de software. Temos um projeto em que a empresa realiza uma campanha de marketing agressiva, resultando em milhões de usuários acessando simultaneamente. O sistema possui uma página web onde os clientes preenchem um formulário e, ao final, enviam o cadastro. O objetivo é que os usuários recebam apenas uma mensagem de confirmação de que o cadastro foi efetuado com sucesso, aguardando um possível sorteio no futuro.

Descrevendo o problema enfrentado

Nossa equipe de desenvolvimento criou uma página web com um micro-serviço que atende a essa página e envia os dados por meio de uma chamada REST HTTP para outro serviço de cadastro, que salva as informações no banco de dados. No entanto, durante o primeiro disparo da campanha, o sistema suportou apenas cinco minutos antes de cair, causando indisponibilidade e prejudicando a imagem da empresa. Mesmo após aumentar a memória, CPU e escalar os micro-serviços, o problema persistiu, especialmente no back-end de cadastro, que não suportava a quantidade de cadastros.

Fomos chamados para revisitar essa arquitetura e encontrar uma solução rápida, sem reescrever tudo, pois a empresa planejava outra campanha na semana seguinte. Precisamos garantir que o site permaneça estável e que os clientes consigam se cadastrar sem problemas, evitando desperdício de investimento em marketing.

Analisando a arquitetura atual

Aqui está o desenho da arquitetura mencionada. Temos a campanha de marketing representada pelos clientes acessando o sistema front-end, que faz uma requisição HTTP para o back-end for front-end, e este, por sua vez, faz outra chamada para os serviços back-end que realizam o cadastro no banco de dados. O sistema verde é o que falha, e a falha ocorre na chamada HTTP.

A arquitetura é simples, mas não está conseguindo escalar para suportar milhões de requisições. Precisamos analisar o que deve ser entregue em tempo real para o usuário e o que pode ser entregue futuramente. Devemos questionar se o processamento precisa ser síncrono ou se pode ser assíncrono, enviando a requisição para uma fila ou tópico para processamento posterior. Mensagens síncronas são mais difíceis de escalar e impactam o cliente em caso de instabilidade. Já as assíncronas permitem que o consumidor processe na velocidade que consegue, sem acoplamento direto com o produtor.

Propondo uma solução assíncrona

Neste cenário, onde o usuário apenas recebe um feedback de cadastro efetuado, podemos transformar o processamento de síncrono para assíncrono. O usuário não precisa esperar o cadastro ser concluído imediatamente. Podemos enviar o evento para um tópico Kafka e devolver a mensagem de sucesso ao usuário, enquanto o back-end processa o evento posteriormente.

Com essa abordagem, o sistema nunca cairá, apenas poderá haver um atraso no cadastro. A experiência do usuário será sempre positiva, pois o front-end e o back-end azul permanecerão online. A campanha de marketing será um sucesso, pois todos conseguirão se cadastrar, e o evento será processado no Kafka em algum momento.

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

Vimos que existem dois tipos principais de comunicação nos sistemas: síncrona e assíncrona. Na comunicação síncrona, a execução é bloqueante, aguardando a resposta. Na assíncrona, é não bloqueante, permitindo que o evento seja processado futuramente. A comunicação síncrona requer que todos os sistemas estejam disponíveis simultaneamente, enquanto a assíncrona permite um acoplamento temporal mais flexível.

Os protocolos típicos de mensagens síncronas incluem HTTP REST, gRPC e GraphQL, enquanto para comunicação assíncrona temos MQTT, Kafka e JMS. Nem sempre é possível usar apenas um tipo de comunicação; devemos analisar os trade-offs e escolher a melhor abordagem para cada situação.

Avaliando os desafios e benefícios

Em resumo, a comunicação síncrona pode apresentar problemas de latência e responsividade, enquanto a assíncrona oferece maior flexibilidade e escalabilidade. Devemos sempre considerar os benefícios e desafios de cada abordagem ao projetar a arquitetura de um sistema.

A comunicação síncrona proporciona uma resposta imediata à ação realizada com sucesso, mas pode aumentar a latência. Se o serviço apresentar algum gargalo ou lentidão, o usuário perceberá imediatamente. Por outro lado, a comunicação assíncrona melhora a responsividade do sistema, pois podemos postar no Kafka e o consumidor não precisa se preocupar com isso. Em termos de throughput (vazão) e escalabilidade, os sistemas assíncronos conseguem lidar com um volume muito maior de requisições em um curto espaço de tempo, processando de forma assíncrona. O processamento ocorre no consumidor em um segundo momento.

Considerando a consistência e complexidade

Entretanto, a comunicação assíncrona apresenta desafios relacionados à consistência dos dados. Na comunicação síncrona, como há sincronismo com as requisições, a consistência dos dados é imediata. Quando o usuário solicita um dado, ele é buscado no banco e está cadastrado. Assim, o estado do banco reflete a requisição do usuário. Por outro lado, em uma requisição assíncrona, se a mensagem não for consumida, o dado ainda não estará no banco, podendo resultar em uma consistência eventual, o que é mais difícil de gerenciar. Existem requisitos adicionais a serem trabalhados para garantir uma consistência forte em uma comunicação assíncrona.

Sistemas assíncronos são geralmente mais complexos. Implementar uma chamada REST, criar um cliente em um servidor e desenvolver uma API é mais simples. No entanto, ao lidar com mensagens assíncronas, é necessário envolver um terceiro sistema, como Kafka, Rabbit ou um sistema SaaS de mensageria, o que aumenta a complexidade. Isso implica em mais um fator a ser monitorado e acompanhado de perto, além de evoluir como plataforma.

Explorando padrões de comunicação

Hoje, estamos comparando comunicação síncrona e assíncrona. Existem formas de mitigar os desafios, que abordaremos ao aprofundar na comunicação síncrona. Vamos discutir os padrões de comunicação, como o famoso request-response (requisição-resposta) e o publish-subscribe (publicar-assinar). Quando um serviço publica uma mensagem, outros consumidores se inscrevem nos tópicos e leem essa mensagem. Temos um produtor que faz a publicação e vários consumidores lendo a mensagem, conhecido como pub-sub.

Existem também padrões avançados de integração, como o message router (roteador de mensagens), que direciona uma mensagem com base em seu conteúdo. Um exemplo é quando precisamos enviar uma notificação push para o dispositivo móvel do usuário ou um e-mail. O produtor não precisa saber se deve enviar um e-mail ou push. Ele simplesmente envia a mensagem para o tópico, que decide se deve encaminhá-la para um sistema consumidor de e-mails ou de push, com base no conteúdo da mensagem.

Implementando padrões de resiliência

Outro padrão é o splitter, que divide uma mensagem complexa em mensagens menores, distribuindo o processamento entre vários consumidores. Isso aumenta o paralelismo e é útil em cenários complexos. O oposto é o aggregator, que combina o resultado de várias mensagens em uma única. A mensageria agrupa as mensagens em uma só e envia para o consumidor.

Em ambos os casos, é importante tratar erros. Existem padrões como o circuit breaker, que simula um circuito eletrônico. Se o circuito estiver aberto, ele para de enviar a mensagem, permitindo que o serviço dependente se recupere. Os timeouts definem o tempo máximo de espera para uma requisição, evitando que o cliente seja sobrecarregado. O retry (retentativa) permite tentar novamente algumas chamadas, mas deve ser usado com cautela em transações financeiras para evitar duplicações. O fallback é uma alternativa quando um sistema falha, redirecionando para outro sistema.

Concluindo a discussão

Para concluir, a escolha entre comunicação síncrona e assíncrona deve ser baseada nas regras de negócios, considerando os trade-offs. A comunicação síncrona oferece simplicidade, mas com acoplamento forte. A mensageria assíncrona proporciona escalabilidade, mas é mais complexa. Adotar padrões de comunicação, como Publish-Subscribe e DLQ, é fundamental. Versionar APIs e esquemas de mensagens é importante, assim como estabelecer um padrão de observabilidade e monitoramento. Métricas básicas, como latência, throughput e taxa de erros, são essenciais para mensagens síncronas e assíncronas.

Ficamos por aqui. Obrigado. Até mais.

Fundamentos de Comunicação entre Serviços - Rest vs gRPC vs GraphQL

Introduzindo o tema da aula

Olá! Sejam bem-vindos a mais uma aula. Hoje, vamos abordar REST, gRPC e GraphQL. Exploraremos as três tecnologias e suas diferenças. São tecnologias voltadas para comunicação síncrona. Já discutimos bastante sobre mensageria e agora vamos nos aprofundar mais em comunicação síncrona, diferenciando esses três padrões e quando utilizá-los em cada caso.

Antes de começarmos, vamos imaginar um cenário em que um time de desenvolvimento está em uma reunião. Nós somos chamados para essa reunião porque as pessoas desenvolvedoras não chegaram a um consenso sobre qual tecnologia síncrona devem usar. Uma pessoa desenvolvedora sênior sugere o uso de REST, enquanto outra, de nível pleno, viu em um evento que gRPC é a melhor opção. Por outro lado, outra pessoa desenvolvedora defende o uso de GraphQL, pois aprendeu em um curso que seria a melhor tecnologia. Precisamos decidir ou ajudar a chegar a um consenso sobre qual seria a melhor tecnologia. O que devemos responder? Claro que depende da situação. Quais perguntas devemos fazer para entender o contexto e decidir qual é a melhor tecnologia? Todas as três tecnologias, REST, gRPC e GraphQL, têm suas vantagens e desvantagens.

Explorando as tecnologias REST, gRPC e GraphQL

Vamos nos aprofundar nessas três tecnologias e técnicas, analisando suas vantagens, desvantagens e como mitigar essas desvantagens. REST é a mais utilizada, usando o protocolo HTTP, geralmente em conjunto com JSON, para chamadas principais e integrações entre front-end e back-end, mobile e back-end, e entre back-ends. Mais recentemente, surgiu o gRPC, voltado para comunicação entre back-ends, aproveitando o HTTP2. Por fim, temos o GraphQL, voltado para busca e leitura de dados de maneira dinâmica, com filtros dinâmicos.

Aqui temos um panorama das três camadas. Um desenho ilustra onde podemos usar cada tecnologia de maneira exemplificada e em alto nível. Temos clientes web e aplicativos que podem fazer chamadas GraphQL ou REST, e o back-end pode fazer chamadas REST ou gRPC entre si.

Analisando os fundamentos dos protocolos

Quais são os fundamentos dos protocolos? REST é o mais simples e antigo, identificado por URLs, usando verbos HTTP como GET, POST, PUT, PATCH e DELETE. É stateless (sem estado), o que ajuda na escalabilidade, e se baseia no protocolo HTTP, aproveitando status de resposta, códigos de status e cabeçalhos para informações complementares. REST aceita JSON e XML, embora JSON seja mais comum atualmente.

O gRPC é uma tecnologia mais nova, criada para resolver problemas de comunicação entre micro-serviços, melhorando a performance. Utiliza comunicação binária com protocol buffers, em cima do HTTP2, eliminando o overhead da serialização. Possui um contrato forte com proto files, garantindo um contrato mais robusto que uma API JSON. Permite streaming unidirecional e bidirecional para comunicação em tempo real.

O GraphQL usa JSON e é semelhante ao REST, mas com um esquema voltado para buscas, permitindo flexibilidade maior. Aceita apenas JSON, enquanto o gRPC usa protocol buffers binários e o REST aceita JSON e XML. REST funciona em HTTP1 e HTTP2, gRPC apenas em HTTP2, e GraphQL em qualquer versão HTTP.

Comparando definições de APIs e documentações

As definições de APIs e documentações variam: REST usa OpenAPI e Swagger, gRPC usa proto files, e GraphQL pode usar GraphiQL ou CodeGen. O streaming no REST é limitado, enquanto no gRPC é nativo e no GraphQL pode ser feito via subscriptions.

REST é mais simples, com cache nativo e ferramentas maduras. gRPC é mais performático, com contratos rígidos e streaming bidirecional. GraphQL oferece flexibilidade para clientes, com consultas dinâmicas semelhantes a SQL para APIs.

Avaliando vantagens e desvantagens

As desvantagens incluem múltiplas requisições no REST, complexidade no gRPC e no GraphQL, e dificuldades de caching. REST é indicado para APIs públicas, CRUDs simples e integrações. gRPC é recomendado para comunicação interna entre micro-serviços e IoT. GraphQL é ideal para front-ends com dados complexos e dashboards.

Comparando a performance, REST requer muitas requisições, aumentando o número de roundtrips. gRPC permite multiplexação de requisições no HTTP2, enquanto GraphQL consolida chamadas em uma única requisição. A serialização no REST é mais custosa, enquanto no gRPC é mais compacta e performática. No GraphQL, há menos chamadas, reduzindo o overhead de parsing.

Otimizando o uso de protocolos

Se estivermos utilizando HTTP2, conseguimos obter certos benefícios. Caso contrário, se estivermos em HTTP1, não teremos esses benefícios, mas podemos operar de maneira agnóstica em relação ao protocolo. Como podemos otimizar nosso payload? No caso do JSON e do REST, eles são mais verbosos, mas isso tem um lado positivo: é mais fácil de ler, depurar e é mais amigável para a pessoa desenvolvedora. Usar REST com JSON ou XML é mais amigável, pois permite ler facilmente os campos que estão sendo trafegados, bem como as requisições e respostas.

Podemos minimizar o tempo de parsing utilizando headers de compressão, como gzip ou Brotli, reduzindo o tamanho do JSON em 70% ou 80%. O gRPC, por outro lado, já é menor nativamente, mas utiliza arquivos binários, o que dificulta a visualização do que está sendo trafegado, tornando-o menos amigável para a pessoa desenvolvedora. Com GraphQL, o JSON é dinâmico, permitindo escolher os campos desejados. Se precisarmos de um campo, podemos selecionar apenas esse campo; se precisarmos de dez campos, podemos selecionar todos eles. O payload é dinâmico e podemos usar headers para minimizar e comprimir os dados. Se utilizarmos conscientemente apenas os campos necessários, o GraphQL será reduzido. No REST, ao enviar uma requisição, sempre recebemos todos os campos, independentemente de quantos utilizamos. No GraphQL, podemos selecionar apenas um ou dois campos de uma lista de dez, por exemplo.

Considerando a escalabilidade e cacheabilidade

O REST é escalável e cacheável, permitindo múltiplas requisições e facilitando a documentação de APIs públicas. O gRPC, embora não seja tão simples de usar, oferece a vantagem de permitir a abertura de um canal para múltiplas chamadas, aproveitando o HTTP2. O gRPC é indicado para comunicação entre back-ends. O GraphQL é útil quando precisamos fazer muitas consultas em uma única requisição, otimizando e evitando problemas de "n+1".

Para otimizar ainda mais nossas APIs REST, é recomendado usar um header de compressão, garantindo a redução da serialização e melhorando o desempenho. O gRPC já utiliza protocol buffers, otimizando os payloads. O problema "n+1" é comum em APIs REST, onde precisamos seguir padrões RESTful e criar APIs granulares para cada tipo de requisição. Isso pode resultar em múltiplas requisições para compor dados complexos, aumentando a latência e a sobrecarga de rede e servidor. No GraphQL, podemos consolidar consultas em uma única chamada, resolvendo o problema "n+1". No gRPC, minimizamos esse problema com uma conexão aberta e bidirecional, permitindo várias requisições dentro de uma chamada.

Implementando práticas de otimização

Para otimizar ainda mais nossas APIs, podemos usar query params, cache, paginação e filtragem de dados no REST. No gRPC, a comunicação bidirecional permite reutilização de conexões e compressão de dados nativa. No GraphQL, podemos usar data loaders e batch loading, limitar queries para prevenir ataques de DOS e usar queries pré-prontas para otimizar o desempenho.

Todas as nossas APIs, sejam REST, gRPC ou GraphQL, precisam de versionamento desde o início. No REST, o versionamento pode ser feito no header, na URL ou em query params. Adicionar novos campos é compatível, mas remover ou renomear campos resulta em uma quebra de versão. No gRPC, o protocol buffer possui regras claras de compatibilidade, e no GraphQL, a evolução contínua do schema permite adicionar campos sem quebra de versão. Campos depreciados podem ser indicados com a anotação @deprecated.

Aplicando tecnologias em casos práticos

Em casos práticos, usamos REST para APIs públicas e integrações com parceiros, como Twitter, Stripe, PayPal e GitHub. Para comunicação entre micro-serviços, utilizamos gRPC, especialmente quando há necessidade de alta performance ou streaming de dados. O GraphQL é ideal para aplicações móveis, onde a eficiência de rede e a flexibilidade são essenciais.

REST, gRPC e GraphQL são tecnologias complementares, podendo ser usadas juntas em uma mesma arquitetura. REST é priorizado pela simplicidade e ecossistema maduro, gRPC para comunicação entre back-ends e streaming de dados, e GraphQL para flexibilidade em consultas complexas. Estudar e aprofundar-se nessas tecnologias e suas melhores práticas é essencial. Podemos usar as três tecnologias juntas, aproveitando seus benefícios e mitigando seus problemas.

Concluindo a aula

Encerramos aqui. Obrigado e até a próxima!

Sobre o curso Padrões de Integração em Sistemas Distribuídos

O curso Padrões de Integração em Sistemas Distribuídos possui 296 minutos de vídeos, em um total de 33 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

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

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas