gRPC vs. REST: Introdução ao melhor protocolo de API

Publicados: 2022-07-22

No cenário tecnológico atual, a maioria dos projetos exige o uso de APIs. As APIs conectam a comunicação entre serviços que podem representar um sistema único e complexo, mas também podem residir em máquinas separadas ou usar várias redes ou linguagens incompatíveis.

Muitas tecnologias padrão atendem às necessidades de comunicação entre serviços de sistemas distribuídos, como REST, SOAP, GraphQL ou gRPC. Embora o REST seja uma abordagem preferida, o gRPC é um concorrente digno, oferecendo alto desempenho, contratos tipados e ferramentas excelentes.

Visão geral do REST

A transferência de estado representacional (REST) ​​é um meio de recuperar ou manipular os dados de um serviço. Uma API REST geralmente é construída no protocolo HTTP, usando um URI para selecionar um recurso e um verbo HTTP (por exemplo, GET, PUT, POST) para selecionar a operação desejada. Os corpos de solicitação e resposta contêm dados específicos da operação, enquanto seus cabeçalhos fornecem metadados. Para ilustrar, vejamos um exemplo simplificado de recuperação de um produto por meio de uma API REST.

Aqui, solicitamos um recurso de produto com um ID de 11 e direcionamos a API para responder no formato JSON:

 GET /products/11 HTTP/1.1 Accept: application/json

Dada esta solicitação, nossa resposta (cabeçalhos irrelevantes omitidos) pode se parecer com:

 HTTP/1.1 200 OK Content-Type: application/json { id: 11, name: "Purple Bowtie", sku: "purbow", price: { amount: 100, currencyCode: "USD" } }

Embora o JSON possa ser legível por humanos, ele não é ideal quando usado entre serviços. A natureza repetitiva de fazer referência a nomes de propriedades, mesmo quando compactadas, pode levar a mensagens inchadas. Vejamos uma alternativa para resolver essa preocupação.

Visão geral do gRPC

gRPC Remote Procedure Call (gRPC) é um protocolo de comunicação de plataforma cruzada de código aberto, baseado em contrato, que simplifica e gerencia a comunicação entre serviços, expondo um conjunto de funções a clientes externos.

Construído sobre HTTP/2, o gRPC aproveita recursos como streaming bidirecional e Transport Layer Security (TLS) integrado. O gRPC permite uma comunicação mais eficiente por meio de cargas binárias serializadas. Ele usa buffers de protocolo por padrão como seu mecanismo para serialização de dados estruturados, semelhante ao uso de JSON do REST.

Ao contrário do JSON, no entanto, os buffers de protocolo são mais do que um formato serializado. Eles incluem três outras partes principais:

  • Uma linguagem de definição de contrato encontrada em arquivos .proto (Seguiremos proto3, a mais recente especificação de linguagem de buffer de protocolo.)
  • Código de função de acessor gerado
  • Bibliotecas de tempo de execução específicas de linguagem

As funções remotas que estão disponíveis em um serviço (definidas em um arquivo .proto ) são listadas dentro do nó de serviço no arquivo de buffer de protocolo. Como desenvolvedores, podemos definir essas funções e seus parâmetros usando o sistema de tipos ricos de buffers de protocolo. Este sistema suporta vários tipos numéricos e de data, listas, dicionários e valores nulos para definir nossas mensagens de entrada e saída.

Essas definições de serviço precisam estar disponíveis para o servidor e o cliente. Infelizmente, não existe um mecanismo padrão para compartilhar essas definições além de fornecer acesso direto ao próprio arquivo .proto .

Este exemplo de arquivo .proto define uma função para retornar uma entrada de produto, dado um ID:

 syntax = "proto3"; package product; service ProductCatalog { rpc GetProductDetails (ProductDetailsRequest) returns (ProductDetailsReply); } message ProductDetailsRequest { int32 id = 1; } message ProductDetailsReply { int32 id = 1; string name = 2; string sku = 3; Price price = 4; } message Price { float amount = 1; string currencyCode = 2; }
Trecho 1: definição de serviço ProductCatalog

A tipagem estrita e a ordenação de campos do proto3 tornam a desserialização de mensagens consideravelmente menos exigente do que a análise de JSON.

Comparando REST vs. gRPC

Para recapitular, os pontos mais significativos ao comparar REST vs. gRPC são:

DESCANSO gRPC
Multiplataforma Sim Sim
Formato da mensagem Personalizado, mas geralmente JSON ou XML Buffers de protocolo
Tamanho da carga da mensagem Médio Grande Pequena
Complexidade de Processamento Superior (análise de texto) Inferior (estrutura binária bem definida)
Suporte ao navegador Sim (nativo) Sim (via gRPC-Web)

Onde contratos menos rigorosos e adições frequentes à carga útil são esperados, JSON e REST são ótimas opções. Quando os contratos tendem a ficar mais estáticos e a velocidade é de extrema importância, o gRPC geralmente vence. Na maioria dos projetos em que trabalhei, o gRPC provou ser mais leve e com melhor desempenho que o REST.

Implementação do serviço gRPC

Vamos criar um projeto simplificado para explorar como é simples adotar o gRPC.

Criando o projeto de API

Para começar, criaremos um projeto .NET 6 no Visual Studio 2022 Community Edition (VS). Vamos selecionar o modelo ASP.NET Core gRPC Service e nomear o projeto (usaremos InventoryAPI ) e nossa primeira solução dentro dele ( Inventory ) .

Uma caixa de diálogo "Configure your new project" no Visual Studio 2022. Nessa tela, digitamos "InventoryAPI" no campo Project name, selecionamos "C:\MyInventoryService" no campo Location e digitamos "Inventory" no campo Solution name . Deixamos a opção "Colocar solução e projeto no mesmo diretório" desmarcada.

Agora vamos escolher o . NET 6.0 (suporte de longo prazo) para nossa estrutura:

Uma caixa de diálogo de informações adicionais no Visual Studio 2022. Nesta tela, selecionamos ".NET 6.0 (suporte de longo prazo)" no menu suspenso do Framework. Deixamos "Ativar Docker" desmarcado.

Definindo nosso serviço de produto

Agora que criamos o projeto, o VS exibe um serviço de definição de protótipo gRPC de amostra chamado Greeter . Reaproveitaremos os arquivos principais do Greeter para atender às nossas necessidades.

  • Para criar nosso contrato, substituiremos o conteúdo de greet.proto pelo Snippet 1, renomeando o arquivo product.proto .
  • Para criar nosso serviço, substituiremos o conteúdo do arquivo GreeterService.cs pelo Snippet 2, renomeando o arquivo ProductCatalogService.cs .
 using Grpc.Core; using Product; namespace InventoryAPI.Services { public class ProductCatalogService : ProductCatalog.ProductCatalogBase { public override Task<ProductDetailsReply> GetProductDetails( ProductDetailsRequest request, ServerCallContext context) { return Task.FromResult(new ProductDetailsReply { Id = request.Id, Name = "Purple Bowtie", Sku = "purbow", Price = new Price { Amount = 100, CurrencyCode = "USD" } }); } } }
Trecho 2: ProductCatalogService

O serviço agora retorna um produto codificado permanentemente. Para fazer o serviço funcionar, precisamos apenas alterar o registro do serviço em Program.cs para referenciar o novo nome do serviço. No nosso caso, vamos renomear app.MapGrpcService<GreeterService>(); para app.MapGrpcService<ProductCatalogService>(); para tornar nossa nova API executável.

Aviso justo: não é seu teste de protocolo padrão

Embora possamos ficar tentados a experimentá-lo, não podemos testar nosso serviço gRPC por meio de um navegador voltado para seu endpoint. Se tentássemos fazer isso, receberíamos uma mensagem de erro indicando que a comunicação com os endpoints gRPC deve ser feita por meio de um cliente gRPC.

Criando o cliente

Para testar nosso serviço, vamos usar o modelo de aplicativo de console básico do VS e criar um cliente gRPC para chamar a API. Eu nomeei meu InventoryApp .

Por conveniência, vamos referenciar um caminho de arquivo relativo pelo qual compartilharemos nosso contrato. Adicionaremos a referência manualmente ao arquivo .csproj . Em seguida, atualizaremos o caminho e definiremos o modo Client . Observação: recomendo que você se familiarize e tenha confiança em sua estrutura de pastas local antes de usar a referência relativa.

Aqui estão as referências .proto , conforme aparecem nos arquivos de projeto do serviço e do cliente:

Arquivo de projeto de serviço
(Código para copiar para o arquivo de projeto do cliente)
Arquivo de projeto do cliente
(Após colar e editar)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Agora, para chamar nosso serviço, substituiremos o conteúdo de Program.cs . Nosso código cumprirá vários objetivos:

  1. Crie um canal que represente o local do terminal de serviço (a porta pode variar, portanto, consulte o arquivo launchsettings.json para obter o valor real).
  2. Crie o objeto cliente.
  3. Construa uma solicitação simples.
  4. Envie a solicitação.
 using System.Text.Json; using Grpc.Net.Client; using Product; var channel = GrpcChannel.ForAddress("https://localhost:7200"); var client = new ProductCatalog.ProductCatalogClient(channel); var request = new ProductDetailsRequest { Id = 1 }; var response = await client.GetProductDetailsAsync(request); Console.WriteLine(JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true })); Console.ReadKey();
Trecho 3: Novo Program.cs

Preparando-se para o lançamento

Para testar nosso código, no VS, clicaremos com o botão direito do mouse na solução e escolheremos Set Startup Projects . Na caixa de diálogo Solution Property Pages, iremos:

  • Selecione o botão de opção ao lado de Vários projetos de inicialização e, no menu suspenso Ação, defina os dois projetos ( InventoryAPI e InventoryApp ) para Iniciar .
  • Clique em OK .

Agora podemos iniciar a solução clicando em Iniciar na barra de ferramentas do VS (ou pressionando a tecla F5 ). Duas novas janelas de console serão exibidas: uma para nos informar que o serviço está escutando, a outra para nos mostrar detalhes do produto recuperado.

Compartilhamento de contrato gRPC

Agora vamos usar outro método para conectar o cliente gRPC à definição do nosso serviço. A solução de compartilhamento de contrato mais acessível ao cliente é disponibilizar nossas definições por meio de uma URL. Outras opções são muito frágeis (arquivo compartilhado por meio de um caminho) ou exigem mais esforço (contrato compartilhado por meio de um pacote nativo). O compartilhamento por meio de uma URL (como SOAP e Swagger/OpenAPI fazem) é flexível e requer menos código.

Para começar, disponibilize o arquivo .proto como conteúdo estático. Atualizaremos nosso código manualmente porque a interface do usuário na ação de compilação está definida como “Protobuf Compiler”. Essa alteração direciona o compilador para copiar o arquivo .proto para que possa ser servido a partir de um endereço da web. Se essa configuração fosse alterada por meio da interface do usuário do VS, a compilação seria interrompida. Nossa primeira etapa, então, é adicionar o Snippet 4 ao arquivo InventoryAPI.csproj :

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Trecho 4: código a ser adicionado ao arquivo de projeto do serviço InventoryAPI

Em seguida, inserimos o código no Snippet 5 na parte superior do arquivo ProductCatalogService.cs para configurar um endpoint para retornar nosso arquivo .proto :

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Trecho 5: Importações de Namespace

E agora, adicionamos o Snippet 6 antes de app.Run() , também no arquivo ProductCatalogService.cs :

 var provider = new FileExtensionContentTypeProvider(); provider.Mappings.Clear(); provider.Mappings[".proto"] = MediaTypeNames.Text.Plain; app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Protos")), RequestPath = "/proto", ContentTypeProvider = provider }); app.UseRouting();
Trecho 6: código para tornar os arquivos .proto acessíveis por meio da API

Com os Snippets 4-6 adicionados, o conteúdo do arquivo .proto deve estar visível no navegador.

Um novo cliente de teste

Agora queremos criar um novo cliente de console que conectaremos ao nosso servidor existente com o Assistente de Dependência do VS. O problema é que este assistente não fala HTTP/2. Portanto, precisamos ajustar nosso servidor para falar sobre HTTP/1 e iniciar o servidor. Com nosso servidor agora disponibilizando seu arquivo .proto , podemos construir um novo cliente de teste que se conecta ao nosso servidor por meio do assistente gRPC.

  1. Para alterar nosso servidor para falar por HTTP/1, editaremos nosso arquivo JSON appsettings.json :
    1. Ajuste o campo Protocol (encontrado no caminho Kestrel.EndpointDefaults.Protocols ) para ler Https .
    2. Salve o arquivo.
  2. Para que nosso novo cliente leia essas informações proto , o servidor deve estar em execução. Originalmente, iniciamos o cliente anterior e nosso servidor a partir da caixa de diálogo Set Startup Projects do VS. Ajuste a solução do servidor para iniciar apenas o projeto do servidor e, em seguida, inicie a solução. (Agora que modificamos a versão HTTP, nosso cliente antigo não pode mais se comunicar com o servidor.)
  3. Em seguida, crie o novo cliente de teste. Inicie outra instância do VS. Repetiremos as etapas conforme detalhado na seção Criando o projeto de API , mas, desta vez, escolheremos o modelo de aplicativo de console . Chamaremos nosso projeto e solução InventoryAppConnected .
  4. Com o chassi do cliente criado, nos conectaremos ao nosso servidor gRPC. Expanda o novo projeto no VS Solution Explorer.
    1. Clique com o botão direito do mouse em Dependências e, no menu de contexto, selecione Gerenciar serviços conectados .
    2. Na guia Serviços Conectados, clique em Adicionar uma referência de serviço e escolha gRPC .
    3. Na caixa de diálogo Add Service Reference, escolha a opção URL e insira a versão http do endereço do serviço (lembre-se de pegar o número da porta gerado aleatoriamente em launchsettings.json ) .
    4. Clique em Concluir para adicionar uma referência de serviço que pode ser facilmente mantida.

Sinta-se à vontade para verificar seu trabalho em relação ao código de exemplo para este exemplo. Como, sob o capô, o VS gerou o mesmo cliente que usamos em nossa primeira rodada de testes, podemos reutilizar o conteúdo do arquivo Program.cs do serviço anterior literalmente.

Quando alteramos um contrato, precisamos modificar nossa definição de gRPC do cliente para corresponder à definição .proto atualizada. Para fazer isso, precisamos apenas acessar os Serviços Conectados do VS e atualizar a entrada de serviço relevante. Agora, nosso projeto gRPC está completo e é fácil manter nosso serviço e cliente sincronizados.

Seu próximo candidato a projeto: gRPC

Nossa implementação do gRPC fornece uma visão em primeira mão dos benefícios do uso do gRPC. REST e gRPC têm seus próprios casos de uso ideais, dependendo do tipo de contrato. No entanto, quando ambas as opções se encaixam, encorajo você a experimentar o gRPC – ele o colocará à frente da curva no futuro das APIs.