Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Node.js: serviços RPC escaláveis e transmissão de dados

Node.js: serviços RPC escaláveis e transmissão de dados

Comunicação entre micro serviços - Apresentação

Introduzindo o curso sobre Node.js e gRPC

Olá! Sejam muito bem-vindos a este curso sobre Node.js, com foco em gRPC. Vamos começar de maneira sólida, pois, como diz o ditado, "bem começado é metade feito".

Primeiramente, vamos entender o histórico, explorando a origem do gRPC e do RPC, além de compreender o conceito por trás dessa tecnologia. Com os fundamentos bem explicados, passaremos para a implementação prática.

Explorando atividades práticas e integração de tecnologias

Vamos realizar diversas atividades, e alguns trechos de código foram preparados para dar um pouco de spoiler sobre o que está por vir. Iremos combinar gRPC com a API Rest, trabalhar com monorepo, utilizando o pnpm com vários projetos, pacotes e aplicativos, além de um pacote compartilhado.

Neste módulo, vamos trabalhar com um pacote compartilhado que exporta diversas funcionalidades comuns a todos os nossos microsserviços. Implementaremos um método baseado no gRPC para buscar dados, conhecido como NARI. Além disso, aprenderemos a registrar essas funcionalidades de maneira organizada utilizando o pacote compartilhado.

Abordando transmissão de dados em tempo real

No final, abordaremos o conceito de transmissão de dados em tempo real. Vamos criar pedidos e, quando o status de um pedido for alterado, faremos a transmissão de um evento, permitindo que o status do pedido seja atualizado em tempo real.

Teremos a oportunidade de aprender como um serviço se conecta a outro e como podemos utilizar o Postman para receber essas mensagens.

Preparando-se para o módulo e encerrando a introdução

Se já estivermos familiarizados com o NestJS, compreendendo seu funcionamento, e também com o Node.js, estaremos prontos para acompanhar este módulo.

Estamos animados para começar e esperamos que vocês também estejam. Prometemos que estaremos aqui para apoiar até que cheguemos ao próximo vídeo da primeira aula. Nos vemos lá!

Comunicação entre micro serviços - RPC — o problema antes da solução

Revisando o conceito de RPC

Para compreendermos o GRPC, é necessário revisitar o passado e entender o que é o RPC. No início, quando os computadores começaram a se conectar em rede, cada máquina era isolada. As pessoas desenvolvedoras estavam acostumadas a fazer chamadas locais, escrevendo uma função e chamando-a com os parâmetros necessários para obter um resultado. O desafio era manter esse modelo mental, mas permitindo que a função fosse chamada através da rede, em vez de localmente. Esse era o cenário quando o RPC começou a surgir.

Naquela época, tínhamos a ARPANET, precursora da internet, e redes locais LAN. No entanto, não existia ainda a internet comercial, o HTTP, a web como conhecemos hoje, nem a computação em nuvem. As máquinas eram ilhas isoladas, sem conexão ou troca de dados. As funções existiam localmente, e o processo era simples: chamávamos uma função, passávamos parâmetros e armazenávamos o resultado.

Evolução do RPC ao GRPC

A ideia do RPC era fazer uma chamada remota parecer uma chamada local. A pessoa desenvolvedora apenas codificava o resultado, recebendo, por exemplo, calculadora.soma(2, 3). O RPC cuidava de serializar os dados, transmiti-los pela rede, desserializá-los do outro lado, executar a função e retornar o resultado.

Se analisarmos uma linha do tempo, em 1976, começou-se a pensar em chamadas remotas. Em 1984, o SANA RPC popularizou o conceito. Em 1998, com o advento do XML e XMLRPC, tornou-se possível enviar headers no HTTP, permitindo trafegar dados além de HTML, como XML. O XMLRPC era basicamente RPC via HTTP, trafegando XML. No início dos anos 2000, surgiu o SOAP, que, apesar de ter um conceito bom, era complexo e burocrático de implementar. Somente em 2015, o Google abriu o que era conhecido como STUB, que se tornou o GRPC.

Funcionamento do GRPC

O GRPC é o foco do nosso curso. Ele funciona com um fluxo onde temos um código local que chama uma função local. Essa função, chamada de STUB, atua como um intermediário, cuidando de todo o processo de comunicação entre os computadores.

Ele vai serializar os parâmetros, enviar os dados, e a rede transmitirá essas informações. Quando chegam no STUB do outro lado, ele fará o inverso: desserializa os parâmetros, executa a função, retorna o resultado e, então, o processo se repete. Serializa a resposta, envia os dados, transmite, desserializa a resposta e o resultado chega de volta. É isso que queremos.

Explorando o papel do STUB

Qual é o papel do STUB? O cliente, nosso código, pensa, age e executa todas as ações da mesma forma que executamos uma função local. Porém, nos bastidores, ele está chamando esse "impostor", que realiza o trabalho de cuidar do transporte dos dados. Assim, o STUB é nosso representante local. Contudo, o RPC não elimina a complexidade, apenas a esconde. Estamos, metaforicamente, jogando a sujeira para debaixo do tapete. Ele não resolve o problema, apenas oculta a complexidade. Em vez de a pessoa desenvolvedora lidar com uma chamada remota, fazer isso via REST ou HTTP, o RPC cuida disso para nós. No entanto, a complexidade ainda existe, apenas foi abstraída.

Discutindo as falácias da computação distribuída

Começaram a surgir as falácias da computação distribuída. Quais são essas falácias? Vamos lá. Primeiro, a rede é confiável? Não, a rede não é confiável, há muitas coisas que podem acontecer. A latência é zero? Também não. Muitos fatores influenciam a latência. A largura de banda é infinita? Não é infinita. A rede não é segura. A topologia vai mudar. Quem afirma que a topologia nunca muda está enganado. Existe um administrador? Nem sempre. O custo de transporte é zero? Também não é. E a rede é homogênea? Também não é. Estamos assumindo muitas premissas ao delegar isso para o RPC, mas na prática, a situação é bem diferente.

Abordando os problemas das chamadas remotas

Isso nos leva aos problemas reais. Quais são esses problemas? Quando temos uma chamada local, ou ela funciona, ou dá erro. Utilizamos try e catch. Teremos uma resposta imediata, sem ambiguidade. Ou deu certo, ou não deu. Quando temos uma chamada remota, ela pode falhar no meio do caminho, pode demorar indefinidamente e nunca chegar à resposta. Podemos nos perguntar: será que chegou? Será que executou lá? Será que a resposta se perdeu no caminho? São muitos problemas que precisamos lidar ao fazer uma chamada remota.

Refletindo sobre idempotência e chamadas remotas

E se o remoto parece local? Como saberemos se precisamos tratar timeout, retries, idempotência? Vamos abrir um parêntese para falar de idempotência, que é um termo que podemos usar para impressionar em um jantar de família. Idempotência, basicamente, é quando queremos realizar a mesma ação várias vezes e o resultado final deve ser sempre o mesmo. Se somamos 2 mais 2, esperamos que seja sempre 4. Se, nesse meio tempo, chamarmos 2 mais 2 e o resultado for diferente de 4, a operação não é idempotente. Ela não está gerando sempre o mesmo resultado quando os parâmetros são os mesmos.

Conectando o RPC às tecnologias atuais

Pode-se perguntar por que estamos falando de algo tão antigo. O RPC tem uma influência significativa nas tecnologias atuais, como o gRPC, que discutiremos durante o curso, o tRPC e o próprio GraphQL, que se baseiam em conceitos de chamada remota. Todas essas grandes ferramentas atuais descendem dessa ideia dos anos 1970. Os micro serviços geram muitas chamadas remotas, pois temos muitos serviços espalhados. Entender o RPC e o conceito de chamada remota é compreender o fundamento de tudo isso. É a base do que temos hoje em termos de chamada remota.

Concluindo o primeiro encontro

Para concluir este primeiro encontro, deixamos uma provocação sem resposta. Pense sobre isso: se o RPC tenta fazer o remoto parecer local, quais problemas surgem quando a rede falha? Quando a rede falha, o meio falha, e não é responsabilidade nem do cliente nem do servidor. Quais problemas isso cria? Reflita sobre isso. Se tiver dúvidas ou quiser discutir, participe do fórum.

Este primeiro assunto foi para estabelecer o cenário da época, por isso a referência ao DeLorean, do filme "De Volta para o Futuro". Se ainda não assistiu, recomendamos que veja. Fizemos essa viagem no tempo para entender o contexto da época. Sem compreender o conceito da época, é difícil entender o problema que estamos resolvendo e como estamos resolvendo.

O encontro de agora está encerrado. No próximo encontro, começaremos a falar diretamente sobre o gRPC. Já entendemos o que é o RPC e por que ele existe. Agora, vamos explorar o gRPC, entender o que ele é, como funciona e como se encaixa. Vamos fazer isso no próximo vídeo. Até mais.

Comunicação entre micro serviços - gRPC — a evolução do RPC

Introduzindo o cenário do GRPC

Vamos dar sequência e falar sobre o GRPC. Qual era o cenário da época? No Google, havia milhões de requisições e um grande número de microserviços. Como fazer esses serviços conversarem de maneira escalável, com performance e fácil manutenção? Vamos viajar no tempo para entender isso. Nos anos 2000, já tínhamos um cenário com milhares de microserviços, dezenas de linguagens diferentes e 10 bilhões de requisições por segundo. O REST, sozinho, não conseguia atender a essa complexidade. Ele não foi pensado para algo desse nível.

Para resolver isso, surgiu o STUB, uma solução interna do Google. Era um sistema proprietário de IRPC, rápido e eficiente, mas preso à infraestrutura do próprio Google. Em 2015, o Google abriu o IRPC, tornando-o open source (código aberto), multilinguagem e pronto para produção. Já estava testado e maduro, pois funcionava internamente no Google há muito tempo.

Explicando o que é o GRPC

O que é, afinal, o GRPC? Ele não é um protocolo, mas um framework de comunicação. Ele reuniu peças que já existiam na época, como o protocol buffers, que é responsável por serializar e deserializar dados, o HTTP2, que melhora a performance e comunicação dos dados, e a geração de código. O GRPC automatiza a criação de código a partir de definições de serviços. Esses são os três pilares do nosso framework de comunicação de IRPC.

Para ilustrar como o protocol buffers funciona, vamos ver um exemplo de definição de serviço. Aqui está um exemplo de um serviço chamado "Greeter", que possui uma chamada remota chamada SayHello:

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

Detalhando o uso do protocol buffers

Neste exemplo, o SayHello é uma chamada remota que recebe uma HelloRequest e retorna um HelloReply. Agora, vamos definir a mensagem HelloRequest:

message HelloRequest {
  string name = 1;
}

O que é o protocol buffers? É um arquivo de definição com palavras reservadas como service, RPC e message. No exemplo acima, um HelloRequest é uma string com uma propriedade chamada name. O protocol buffers é binário, não trafega texto, o que o torna menor e mais rápido. Além disso, permite tipagem, pois estamos definindo o que recebemos e enviamos.

Destacando as vantagens do HTTP2

Por que destacamos o HTTP2? Antes, no HTTP1, tínhamos uma requisição por vez, headers repetidos e o streaming não existia no sentido literal. Com o HTTP2, passamos a ter mais requisições simultâneas, headers comprimidos e streaming bilateral, ou seja, na ida e na volta, de forma nativa. O HTTP1 não tinha especificação para isso, mas o HTTP2 sim, com suporte bidirecional.

Explicando a geração de código no GRPC

O terceiro pilar é a geração de código. Temos um arquivo .proto e o proto C, nosso compilador, que gera código em TypeScript, Go, Python, Java, C#, Rust, PHP, entre outros. O proto C cuida disso para nós. O contrato se transforma em código, não apenas documentação, mas código compilado e tipado.

Se esquecermos de atualizar, o build pode falhar. O gRPC não se limita apenas ao conceito essencial de fazer uma requisição e esperar uma resposta; ele oferece muito mais. Vamos explorar as quatro formas de usar o gRPC.

Explorando as formas de uso do GRPC

Primeiro, temos o Unary. A ideia aqui é de unidade, de único. É o mais parecido com uma chamada HTTP normal: fazemos uma requisição e esperamos uma resposta. Nada além disso. Enviamos uma requisição e recebemos uma resposta.

Em seguida, temos o server streaming. Fazemos uma requisição e o servidor envia dados continuamente enquanto necessário. Enquanto houver dados para enviar, ele continuará transmitindo.

O oposto também existe: client streaming. Enviamos dados continuamente enquanto necessário. Quando terminamos, recebemos uma resposta. Por exemplo, ao enviar um arquivo muito grande, podemos fazer um stream desse upload. Quando terminamos de enviar todos os pedaços, o servidor processa e devolve uma resposta.

Também temos o bidirecional. Nesse caso, há transmissão de dados na ida e na volta, mantendo um canal aberto. Em situações como um chat, um jogo multiplayer ou qualquer tipo de colaboração em tempo real, temos esse conceito de stream bidirecional, com dados sendo enviados e recebidos continuamente.

Considerando limitações e desafios do GRPC

No entanto, o gRPC não é uma solução universal. Antes de decidir usá-lo, é importante considerar alguns cenários onde ele pode não ser adequado. Por exemplo, se o cliente é um navegador, o suporte ainda é limitado, então pode não ser a melhor escolha. Se estamos oferecendo uma API pública para terceiros ou vendendo nossa API como um serviço, pode não ser ideal. O gRPC requer um debug fácil, mas trafega dados em formato binário, o que dificulta a compreensão do que está sendo transmitido. Com HTTP, conseguimos visualizar facilmente o que está sendo enviado e recebido.

Além disso, se não pudermos lidar com a curva de aprendizado, pode não ser o momento certo para adotar o gRPC. Estamos lidando com um modelo mental diferente, e talvez não tenhamos tempo para absorver isso agora.

Implementando o GRPC com Next.js

O que faremos hoje? Vamos implementar isso na prática usando o Next.js. Vamos implementar o gRPC, os arquivos .proto e utilizar algumas abstrações do Next.js. O objetivo é focar nos conceitos, sem precisar implementar tudo do zero. O Next.js nos permitirá focar no gRPC, que é o que realmente importa.

Para implementar o gRPC no ambiente JavaScript, utilizamos algumas bibliotecas específicas. Aqui estão algumas delas:

@grpc/grpc-js - implementação JS
@grpc/proto-loader - carrega os .proto
@nestjs/microservices - abstração

Refletindo sobre a compatibilidade e atualização de APIs

Como o Next.js trata o gRPC? Ele atua como uma camada de transporte. A estrutura é a mesma que já conhecemos no Next.js: Controller, Service, Decorator, tudo igual. O que muda é o transporte subjacente.

Deixo uma questão para reflexão: se o gRPC exige um contrato rígido e compilado, como lidamos com mudanças na API sem quebrar os clientes antigos? Essa é uma preocupação específica, pois o gRPC é compilado. Como atualizamos isso mantendo a retrocompatibilidade com os clientes antigos? Pense sobre isso. Se quiser discutir, participe das comunidades da Alura, no Discord, ou no fórum.

Agora que temos os conceitos bem explicados e definidos, vamos partir para a implementação. Como fazemos na prática para implementar um cliente de RPC em um servidor de RPC e ver a mágica acontecer? Até a próxima!

Sobre o curso Node.js: serviços RPC escaláveis e transmissão de dados

O curso Node.js: serviços RPC escaláveis e transmissão de dados possui 151 minutos de vídeos, em um total de 43 atividades. Gostou? Conheça nossos outros cursos de Node.JS 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 Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas