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

À medida que avançamos no desenvolvimento de aplicativos, entre várias coisas, há uma principal coisa sobre a qual estamos menos preocupados: poder de computação. Isso porque, com a 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 em massa 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 enxuta, 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 de RPC, com foco em gRPC, que é a implementação da Google de RPC. 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 chamadas 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 precisa ser invocada do lado do cliente, poderíamos torná-la tão simples quanto uma chamada de método/função? Basicamente, o que um RPC faz é dar a ilusão ao cliente de que está invocando um método local, mas na realidade, está invocando 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 (abordaremos isso mais adiante no artigo).

Passos envolvidos em uma chamada RPC:

Fluxo de sequência RPC

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

RPCs resumem o processo para abaixo:

Isso ocorre porque todas as complicações associadas à criação de um pedido agora são abstraídas de nós (abordaremos 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 isso funciona

Até agora discutimos RPC, que essencialmente significa fazer chamadas de função/método remotamente — dando-nos os benefícios como “definição de contrato estrita,” “abstraindo transmissão e conversão de dados,” “reduzindo 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 estrutura baseada 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 mecanismos de comunicação mais escolhidos 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 também 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 o exemplo abaixo:

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á parecido com isso.

Como Definimos Serviços?

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

Após escrever os tipos de mensagens de requisição e resposta necessárias, o próximo passo é escrever o próprio serviço.

Os serviços gRPC também são definidos em Protocol Buffers e utilizam 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 recebe HelloRequest como entrada e fornece HelloResponse como saída.

Como mencionado, 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 é adicionar ‘stream ‘ antes do argumento de solicitação/resposta, definições de proto para RPCs de streaming e código gerado.

No link do código-base acima:

gRPC Vs. REST

Nós falamos 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 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 (pequenos/médios/grandes). Resultados de Teste 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 do Kafka, e no caso dos protobufs, você não consegue entender nada. 

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

    • O 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 na 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, encontrou 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 é seqüestrada e a transferência de dados é não monitorada. Em uma conexão gRPC, uma vez estabelecida a conexão TCP, 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 características 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 no REST.

Característica

REST

gRPC

Protocolo de Comunicação

Segue o modelo de requisiçã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 a navegadores

Funciona em qualquer lugar

Suporte limitado. É necessário usar gRPC-Web, que é uma extensão para a web e baseia-se 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

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

Cache de requisições

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

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

Novamente, por enquanto, gRPC não possui suporte em navegadores, já que a maioria dos frameworks de UI ainda possui suporte limitado ou inexistente para gRPC. Embora gRPC seja uma escolha automática na maioria dos casos quando se trata de comunicação entre microserviç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 microserviços com múltiplos microserviços leves, onde a eficiência da transmissão de dados é fundamental, 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 preferida.

  • Com as capacidades de streaming do gRPC, aplicativos em tempo real como trading ou OTT se beneficiariam mais do que fazendo polling usando 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 preferida.

Conceitos do gRPC

Balanceamento de Carga

Embora a conexão persistente resolva o problema de latência, ele traz 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 disponíveis 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 de 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 no lado do cliente.

Contrato Limpo

No REST, o contrato entre o cliente e o servidor é documentado, mas não é estrito. Se voltarmos ainda mais para SOAP, os contratos eram expostos através de arquivos wsdl. No REST, expomos contratos através de 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.

No gRPC, o contrato, seja através de arquivos proto ou stubs gerados a partir desses arquivos, é compartilhado tanto com o cliente quanto com 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 estabelecer conexões com o cliente, cuidar da segurança, serialização-desserialização, etc., é abstraída. Tudo o que nos importamos é com os dados.

Considere a base de código abaixo:

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

O cliente utiliza 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) {

 

  // faz algo com 'req'

 

   return &greetpb.GreetingResponse{

    Result: result,

      }, nil

}

```

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

E o stub foi gerado usando o comando de 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 da 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 do cliente que é aplicado no nível da conexão. Também pode incluir credenciais de autenticação/autorização no nível da chamada a serem aplicadas automaticamente a cada chamada no canal. Exemplo de autenticação baseada em certificado.

Ambos ou apenas um desses mecanismos podem ser usados 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 — eles 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 logging aqui.

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

Middleware suportado pelo framework gRPC.

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

Empacotamento

Vamos seguir a rama de empacotamento.

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 import.

Mesmo que você tente gerar um arquivo proto executando a tarefa ‘gen-pkg’ após mencionar o import, 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 arquivos proto para qualquer outra linguagem fossem criados, 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 à proto de solicitação ou resposta e adicionar um valor ao 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, exigem a versionalização dos serviços

  • Opcional embalagem.

Práticas de Código 

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

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

  • Caso a mensagem de resposta esteja 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

O ecossistema 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 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 acessório para grpc-gateway. Ele é capaz de gerar 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 realizar 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 corresponde às suas expectativas predefinidas.

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

Testando Usando POSTMAN

Ao contrário dos testes de APIs REST com o postman ou quaisquer ferramentas equivalentes como o 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 mudanças 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 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’ na barra lateral esquerda

    

  1. Clique no sinal de ‘+’ 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, então seria melhor manter todos os protos dependentes em um único local.

  2. Os passos acima ajudarão a reter 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, determinamos o processo para testar nossas APIs gRPC via POSTMAN. O processo para testar pontos finais gRPC é diferente do que se usa para pontos finais REST com 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 local. Como não há provisão para acessar mensagens proto entre versões no POSTMAN.

Conclusão

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

gRPC como um framework pode ser crucial, especialmente para arquitetura baseada em microsserviç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 consolidando como linguagem de script do lado do servidor, o gRPC está se consolidando como um framework de comunicação padrão.

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