Compreendendo Conceitos, Casos de Uso e Boas Práticas do gRPC

À medida que avançamos no desenvolvimento de aplicações, entre várias coisas, há uma principal coisa sobre a qual estamos menos preocupados: o poder de computação. Por causa da chegada dos provedores de nuvem, estamos menos preocupados em gerenciar centros de dados. Tudo está disponível em segundos sob demanda. Isso leva a um aumento no tamanho dos dados também. Dados grandes são gerados e transportados usando vários meios em solicitações únicas.

Com o aumento no tamanho dos dados, temos atividades como serialização, desserialização e custos de transporte adicionados a eles. Embora não estejamos preocupados com os recursos de computação, a latência se torna uma sobrecarga. Precisamos reduzir o transporte. Muitos protocolos de mensagens foram desenvolvidos no passado para abordar isso. O SOAP era volumoso e o REST é uma versão simplificada, mas precisamos de um framework ainda mais eficiente. É aí que entram as Chamdas de Procedimento Remoto — RPC.

Neste artigo, vamos entender o que é RPC e as várias implementações do RPC, com foco em gRPC, que é a implementação do RPC da Google. Também compararemos REST com RPC e entenderemos vários aspectos do gRPC, incluindo segurança, ferramentas e muito mais.

O que é RPC?

RPC significa Chamdas de Procedimento Remoto. A definição está no próprio nome. Chamadas de procedimento simplesmente significam chamdas de função/método; é a palavra “Remoto” que faz toda a diferença. E se pudéssemos fazer uma chamada de função remotamente?

Simplificando, se uma função reside em um servidor e para ser invocada do lado do cliente, poderíamos torná-la tão simples quanto uma chamada de método/função? Essencialmente, o que um RPC faz é dar a ilusão ao cliente de que está invocando um método local, mas na realidade, invoca um método em uma máquina remota que abstrai as tarefas da camada de rede. A beleza disso é que o contrato é mantido muito rigoroso e transparente (discutiremos isso mais tarde no artigo).

Passos envolvidos em uma chamada RPC:

Fluxo de sequência RPC

Isto é o que um processo típico do REST se parece:

RPCs resumem o processo abaixo:

Isso porque todas as complicações associadas a fazer um pedido agora são abstraídas de nós (discutiremos isso na geração de código). Tudo o que precisamos nos preocupar é com os dados e a lógica.

gRPC: O que, por que e como fazê-lo

Até agora discutimos o RPC, que essencialmente significa fazer chamadas de função/método remotamente – dando-nos os benefícios como “definição de contrato rigorosa,” “abstraindo a transmissão e conversão de dados,” “reduzindo a latência,” e assim por diante, que discutiremos à medida que avançamos com esta postagem. O que realmente gostaríamos de aprofundar é uma das implementações do RPC. RPC é um conceito e gRPC é uma framework baseado nele.

Existem várias implementações de RPCs. Eles são:

  • gRPC (google)

  • Thrift (Facebook)

  • Finalge (Twitter)

A versão do RPC da Google é conhecida como gRPC. Foi introduzida em 2015 e tem ganhado tração desde então. É uma das principais mecanismos de comunicação em uma arquitetura de microsserviços.

O gRPC utiliza protocol buffers (um formato de mensagem de código aberto) como o método padrão de comunicação entre cliente e servidor. Além disso, o gRPC usa o HTTP/2 como protocolo padrão. Existem quatro tipos de comunicação que o gRPC suporta:

Vamos agora ao formato de mensagem amplamente utilizado no gRPC — protocol buffers, também conhecidos como protobufs. Uma mensagem protobuf se parece com algo assim:

message Person {
string name = 1;
string id = 2;
string email = 3;
}

Aqui, ‘Person’ é a mensagem que gostaríamos de transferir (como parte de uma solicitação/resposta) que possui os campos ‘name’ (tipo string), ‘id’ (tipo string) e ‘email’ (tipo string). Os números 1, 2, 3 representam a posição dos dados (como em ‘name’, ‘id’ e ‘has_ponycopter’) quando são serializados em formato binário.

Uma vez que o desenvolvedor tenha criado o(s) arquivo(s) Protocol Buffer com todas as mensagens, podemos usar um compilador de protocolo (um binário) para compilar o arquivo de protocolo escrito, o que gerará todas as classes e métodos de utilitários necessários para trabalhar com a mensagem. Por exemplo, como mostrado aqui, o código gerado (dependendo da linguagem escolhida) será semelhante a isso.

Como Definimos Serviços?

Precisamos definir serviços que usem as mensagens acima para serem enviadas/recebidas.

Após escrever os tipos de mensagem de solicitação e resposta necessários, o próximo passo é escrever o próprio serviço.

Os serviços gRPC também são definidos em Protocol Buffers e usam as palavras-chave “service” e “RPC” para definir um serviço.

Dê uma olhada no conteúdo do arquivo proto abaixo:

message HelloRequest {
string name = 1;
string description = 2;
int32 id = 3;
}

message HelloResponse {
string processedMessage = 1;
}

service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}

Aqui, HelloRequest e HelloResponse são as mensagens e HelloService está expondo um RPC unário chamado SayHello que leva HelloRequest como entrada e fornece HelloResponse como saída.

Como mencionado, o HelloService atualmente contém um único RPC unário. Mas poderia conter mais de um RPC. Além disso, pode conter uma variedade de RPC (unário/streaming do lado do cliente/streaming do lado do servidor/Bidirecional).

Para definir um RPC de streaming, tudo o que você precisa fazer é prefixar ‘stream ‘ antes do argumento de requisição/resposta, Definições de protótipo de RPCs de streaming e código gerado.

No link do código-base acima:

gRPC vs. REST

Nós conversamos bastante sobre gRPC. Além disso, houve uma menção ao REST. O que perdemos foi discutir a diferença. Quero dizer, quando temos uma estrutura de comunicação leve e bem estabelecida na forma de REST, por que havia a necessidade de procurar outra estrutura de comunicação? Vamos entender mais sobre gRPC em relação a REST, juntamente com os prós e contras de cada um.

Para comparar, o que precisamos são parâmetros. Então, vamos dividir a comparação nos seguintes parâmetros:

  • Formato de mensagem: protocol buffers vs JSON

    • A velocidade de serialização e desserialização é muito melhor no caso dos protocol buffers em todos os tamanhos de dados (pequeno/médio/grande). Resultados dos testes de benchmark

    • Após a serialização, o JSON é legível por humanos, enquanto os protobufs (em formato binário) não são. Não tenho certeza se isso é uma desvantagem ou não, porque às vezes você gostaria de ver os detalhes da solicitação no ferramenta de desenvolvedores do Google ou tópicos Kafka e, no caso dos protobufs, você não consegue entender nada. 

  • Protocolo de comunicação: HTTP 1.1 vs. HTTP/2T

    • REST é baseado em HTTP 1.1; a comunicação entre um cliente REST e um servidor exigiria uma conexão TCP estabelecida, que por sua vez envolve uma mão de três etapas. Quando recebemos uma resposta do servidor após enviar uma solicitação do cliente, a conexão TCP não existe após isso. É necessário criar uma nova conexão TCP para processar outra solicitação. Esse estabelecimento de uma conexão TCP em cada e todas as solicitações contribui para a latência.

    • Então gRPC, que é baseado em HTTP 2, enfrentou esse desafio mantendo uma conexão persistente. Devemos lembrar que as conexões persistentes em HTTP 2 são diferentes daquelas em web sockets, onde uma conexão TCP é sequestrada e a transferência de dados é não monitorada. Em uma conexão gRPC, uma vez que uma conexão TCP é estabelecida, ela é reutilizada para várias solicitações. Todas as solicitações de um par de cliente e servidor são multiplexadas na mesma conexão TCP.

  • Apenas se preocupando com dados e lógica: Geração de código como cidadão de primeira classe

    • As funcionalidades de geração de código são nativas ao gRPC por meio de seu compilador protoc embutido. Com APIs REST, é necessário usar uma ferramenta de terceiros, como o Swagger, para gerar automaticamente o código para chamadas de API em várias linguagens.

    • No caso do gRPC, ele abstrai o processo de marshalling/unmarshalling, configuração de conexão e envio/recebimento de mensagens; o que todos precisamos nos preocupar é com os dados que queremos enviar ou receber e a lógica.

  • Velocidade de transmissão

    • Como o formato binário é muito mais leve que o formato JSON, a velocidade de transmissão no caso do gRPC é 7 a 10 vezes mais rápida do que a do REST.

Característica

REST

gRPC

Protocolo de Comunicação

Segue o modelo de solicitação-resposta. Pode funcionar com qualquer versão do HTTP, mas geralmente é usado com o HTTP 1.1

Segue o modelo de cliente-resposta e é baseado no HTTP 2. Alguns servidores têm soluções alternativas para fazê-lo funcionar com o HTTP 1.1 (via gateways REST)

Suporte ao navegador

Funciona em todos os lugares

Suporte limitado. É necessário usar gRPC-Web, que é uma extensão para a web e baseada em HTTP 1.1

Estrutura de dados de payload

Utiliza principalmente payloads baseados em JSON e XML para transmitir dados

Utiliza protocol buffers por padrão para transmitir payloads

Geração de código

É necessário usar ferramentas de terceiros como o Swagger para gerar o código do cliente

O gRPC possui suporte nativo para geração de código para várias linguagens

Cache de requisições

Fácil de cachear requisições tanto no lado do cliente quanto do servidor. A maioria dos clientes/servidores suporta nativamente isso (por exemplo, via cookies)

Não suporta cache de requisições/respostas por padrão

Mais uma vez, por enquanto, o gRPC não possui suporte para navegadores, já que a maioria dos frameworks de UI ainda tem suporte limitado ou inexistente para o gRPC. Embora o gRPC seja a escolha automática na maioria dos casos quando se trata de comunicação entre microsserviços internos, não é o mesmo para comunicação externa que requer integração de UI.

Agora que fizemos uma comparação entre ambos os frameworks: gRPC e REST. Qual usar e quando?

  • Em uma arquitetura de microsserviços com múltiplos microsserviços leves, onde a eficiência da transmissão de dados é fundamental, o gRPC seria uma escolha ideal.

  • Se a geração de código com suporte para múltiplas linguagens é um requisito, gRPC deve ser a estrutura de escolha.

  • Com as capacidades de streaming do gRPC, aplicativos em tempo real como trading ou OTT beneficiariam-se mais do que usando polling com REST.

  • Se a largura de banda é uma restrição, gRPC ofereceria muito menor latência e throughput.

  • Se um desenvolvimento mais rápido e iteração de alta velocidade é um requisito, REST deve ser a opção de escolha.

Conceitos do gRPC

Balanceamento de Carga

Embora a conexão persistente resolva o problema de latência, ele acarreta outra desafio na forma de balanceamento de carga. Como o gRPC (ou HTTP2) cria conexões persistentes, mesmo na presença de um balanceador de carga, o cliente forma uma conexão persistente com o servidor que está por trás do balanceador de carga. Isso é análogo a uma sessão pegajosa.

Podemos entender o problema ou desafio através de uma demonstração. E os arquivos de código e implantação estão presentes em: https://github.com/infracloudio/grpc-blog/tree/master/grpc-loadbalancing.

A partir da base de código da demonstração acima, podemos descobrir que a responsabilidade do balanceamento de carga recai sobre o cliente. Isso leva ao fato de que a vantagem do gRPC, ou seja, a conexão persistente, não existe com essa mudança. Mas o gRPC ainda pode ser usado por suas outras vantagens.

Leia mais sobre balanceamento de carga no gRPC.

Na base de código da demonstração acima, apenas uma estratégia de balanceamento de carga round-robin é usada/mostrada. Mas o gRPC também suporta outra estratégia de balanceamento de carga baseada no cliente chamada OOB “pick-first”.

Além disso, também é suportado o balanceamento de carga personalizado do lado do cliente.

Contrato Limpo

No REST, o contrato entre o cliente e o servidor é documentado, mas não é rigoroso. Se voltarmos ainda mais para o SOAP, os contratos eram expostos via arquivos wsdl. No REST, expomos contratos via Swagger e outras provisões. Mas a rigidez é insuficiente, não podemos ter certeza se o contrato mudou do lado do servidor enquanto o código do cliente está sendo desenvolvido.

Com o gRPC, o contrato, seja através de arquivos proto ou stubs gerados a partir de arquivos proto, é compartilhado com o cliente e o servidor. Isso é como fazer uma chamada de função, mas remotamente. E como estamos fazendo uma chamada de função, sabemos exatamente o que precisamos enviar e o que esperamos como resposta. A complexidade de fazer conexões com o cliente, cuidar da segurança, serialização-desserialização, etc., são abstraídas. Tudo o que nos preocupamos é com os dados.

Considere a base de código abaixo:

https://github.com/infracloudio/grpc-blog/tree/master/greet_app     

O cliente usa o stub (código gerado a partir do arquivo proto) para criar um objeto de cliente e invocar a chamada de função remota: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



cc, err := grpc.Dial(“<server-address>”, opts)

if err != nil {

    log.Fatalf("could not connect: %v", err)

}



c := greetpb.NewGreetServiceClient(cc)



res, err := c.Greet(context.Background(), req)

if err != nil {

    log.Fatalf("error while calling greet rpc : %v", err)

}

```

Da mesma forma, o servidor também usa o mesmo stub (código gerado a partir do arquivo proto) para receber o objeto de solicitação e criar o objeto de resposta: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



func (*server) Greet(_ context.Context, req *greetpb.GreetingRequest) (*greetpb.GreetingResponse, error) {

 

  // faça algo com 'req'

 

   return &greetpb.GreetingResponse{

    Result: result,

      }, nil

}

```

Ambos estão usando o mesmo stub gerado a partir do arquivo proto que reside aqui.

E o stub foi gerado usando o comando do compilador proto abaixo. 

 

```sh

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/pkg/proto/*.proto

```

Segurança

A autenticação e autorização gRPC funciona em dois níveis:

  • A autenticação/autorização no nível de chamada geralmente é tratada por meio de tokens que são aplicados em metadados quando a chamada é feita. Exemplo de autenticação baseada em token.

  • A autenticação no nível do canal usa um certificado de cliente que é aplicado no nível da conexão. Também pode incluir credenciais de autenticação/autorização no nível de chamada a serem aplicadas automaticamente a cada chamada no canal. Exemplo de autenticação baseada em certificado.

Ambas ou apenas uma dessas mecanismos podem ser usadas para ajudar a proteger os serviços.

Middleware

No REST, utilizamos middleware para vários propósitos como:

  • Limitação de taxa

  • Validação pré/pós requisição/resposta

  • Tratar ameaças de segurança

Podemos alcançar o mesmo com gRPC também. A terminologia é diferente no gRPC — são referidos como interceptores, mas realizam atividades semelhantes.

Na branch de middleware do repositório ‘greet_app’, integramos interceptores de logger e Prometheus. 

Veja como os interceptores são configurados para usar pacotes de Prometheus e de logging aqui.

Mas podemos integrar outros pacotes aos interceptores para fins como prevenir pânicos e recuperação (para lidar com exceções), rastreamento, até autenticação, e assim por diante.

Middleware suportado pela framework gRPC.

Empacotamento, Versionamento e Práticas de Código de Arquivos Proto

Empacotamento

Vamos seguir a ramo de embalagem.

Primeiro, comece com ‘Taskfile.yaml’, a tarefa ‘gen-pkg’ diz ‘protoc –proto_path=packaging packaging/*.proto –go_out=packaging’. Isso significa que ‘protoc’ (o compilador) converterá todos os arquivos em ‘packaging/*.proto’ em seus equivalentes em ‘go’, conforme indicado pela flag ‘–go_out=packaging’, no próprio diretório ‘packaging’.

Em segundo lugar, no arquivo ‘processor.proto’, foram definidas 2 mensagens, a saber, ‘CPU’ e ‘GPU’. Enquanto a CPU é uma mensagem simples com 3 campos de tipos de dados internos, a GPU, por outro lado, possui um tipo de dados personalizado chamado ‘Memory’. ‘Memory’ é uma mensagem separada e é definida em um arquivo totalmente diferente.

Então, como você usa a mensagem ‘Memory’ no arquivo ‘processor.proto’? Usando importação.

Mesmo se você tentar gerar um arquivo proto executando a tarefa ‘gen-pkg’ após mencionar a importação, ele lançará um erro. Como, por padrão, ‘protoc’ assume que ambos os arquivos ‘memory.proto’ e ‘processor.proto’ estão em pacotes diferentes. Portanto, você precisa mencionar o mesmo nome de pacote em ambos os arquivos.

O opcional ‘go_package’ indica ao compilador para criar um nome de pacote como ‘pb’ para arquivos go. Se fossem criados arquivos proto para qualquer outra linguagem, o nome do pacote seria ‘laptop_pkg’.

Versionamento

  • Existem dois tipos de alterações em gRPC: quebrantes e não quebrantes.

  • Alterações não quebrantes incluem adicionar um novo serviço, adicionar um novo método a um serviço, adicionar um campo a um proto de solicitação ou resposta e adicionar um valor a um enum

  • Alterações quebrantes, como renomear um campo, alterar o tipo de dados do campo, número do campo, renomear ou remover um pacote, serviço ou métodos, requerem versãoamento dos serviços

  • Opcional embalagem.

Práticas de Código 

  • A mensagem de solicitação deve terminar com a palavra ‘request’, como `CreateUserRequest`

  • A mensagem de resposta deve terminar com a palavra ‘response’, como `CreateUserResponse`

  • No caso de a mensagem de resposta estar vazia, você pode usar um objeto vazio `CreateUserResponse` ou usar o `google.protobuf.Empty`

  • O nome do pacote deve fazer sentido e deve ser versionado, por exemplo: pacote `com.ic.internal_api.service1.v1`

Ferramentas

A ecosystema gRPC suporta uma variedade de ferramentas para facilitar tarefas não relacionadas ao desenvolvimento, como documentação, gateway REST para um servidor gRPC, integração de validadores personalizados, linting, etc. Aqui estão algumas ferramentas que podem nos ajudar a alcançar o mesmo:

  • protoc-gen-grpc-gateway — plugin para criar um gateway REST de API gRPC. Ele permite que os endpoints gRPC sejam endpoints REST API e realiza a tradução de JSON para proto. Basicamente, você define um serviço gRPC com algumas anotações personalizadas e ele torna esses métodos gRPC acessíveis via REST usando solicitações JSON.

  • protoc-gen-swagger — um plugin complementar para grpc-gateway. Ele é capaz de gerar o arquivo swagger.json com base nas anotações personalizadas necessárias para o gateway gRPC. Você pode importar esse arquivo em seu cliente REST de escolha (como Postman) e executar chamadas de API REST aos métodos que você expôs.

  • protoc-gen-grpc-web — um plugin que permite que nossa interface do usuário frontal se comunique com o backend usando chamadas gRPC. Um post separado sobre isso em breve no futuro.

  • protoc-gen-go-validators — um plugin que permite definir regras de validação para campos de mensagens proto. Ele gera um método de erro Validate() para mensagens proto que você pode chamar em GoLang para validar se a mensagem atende às suas expectativas prédefinidas.

  • https://github.com/yoheimuta/protolint — um plugin para adicionar regras de lint aos arquivos proto

Testando Usando POSTMAN

AO contrário de testar APIs REST com POSTMAN ou quaisquer ferramentas equivalentes como Insomnia, não é muito confortável testar serviços gRPC.

Nota: Os serviços gRPC também podem ser testados a partir da CLI usando ferramentas como evans-cli. Mas para isso, é necessário habilitar a reflexão (se não estiver habilitada, o caminho para o arquivo proto é necessário). As alterações a serem feitas para habilitar a reflexão e como entrar no modo repl do evans-cli. Após entrar no modo repl do evans-cli, os serviços gRPC podem ser testados diretamente a partir da CLI, e o processo é descrito na página do github do evans-cli.

O Postman possui uma versão beta para testar serviços gRPC.

Aqui estão os passos de como você pode fazer isso:

  1. Abra o Postman

  2. Vá para ‘APIs’ no lado esquerdo da barra lateral

    

  1. Clique no sinal ‘+’ para criar um novo API:

    

  1. Na janela pop-up, insira ‘Nome’, ‘Versão’ e ‘Detalhes do Esquema’ e clique em criar [a menos que você precise importar de algumas fontes como github/bitbucket]. Este passo é relevante se você deseja copiar e colar o contrato proto.

5. Sua API é criada como mostrado abaixo. Clique na versão ‘1.0.0’, vá para a definição e insira seu contrato proto.

  1. Lembre-se de que a importação não funciona aqui, por isso é melhor manter todos os protos dependentes em um único local.

  2. Os passos acima ajudarão a manter os contratos para uso futuro.

  3. Em seguida, clique em ‘Novo’ e selecione ‘Solicitação gRPC’:

  1. Insira o URI e escolha o proto na lista de APIs salvas:

    

  1. Insira sua mensagem de solicitação e ‘Invocar’:

Nos passos acima, descobrimos o processo para testar nossas APIs gRPC via POSTMAN. O processo para testar pontos de extremidade gRPC é diferente do de pontos de extremidade REST usando POSTMAN. Uma coisa a lembrar é que ao criar e salvar o contrato proto como em #5, todas as definições de mensagens e serviços proto precisam estar no mesmo lugar. Como não há provisão para acessar mensagens proto em diferentes versões no POSTMAN.

Conclusão

Neste post, desenvolvemos uma ideia sobre RPC, traçamos paralelos com REST e discutimos suas diferenças, então passamos a discutir uma implementação de RPC, ou seja, gRPC desenvolvido pelo Google. 

gRPC como um framework pode ser crucial, especialmente para arquitetura baseada em microserviços para comunicação interna. Pode ser usado para comunicação externa também, mas exigirá um gateway REST. gRPC é essencial para aplicativos de streaming e em tempo real. 

A maneira como o Golang está se provando como uma linguagem de script do lado do servidor, o gRPC está se estabelecendo como um framework de comunicação padrão.

Source:
https://dzone.com/articles/understanding-grpc-concepts-use-cases-amp-best-pra