O relacionamento entre C # e C #: REST, gRPC e tudo mais

Existem várias maneiras de se comunicar entre um cliente C # e um servidor C #. Alguns deles são confiáveis, outros não. Alguns são muito rápidos, outros não. É importante conhecer as várias opções para que você possa decidir qual é o melhor para você. Este artigo discutirá as tecnologias mais populares até o momento e por que elas são tão amplamente usadas. Falaremos sobre REST, gRPC e tudo mais.

Cenário ideal


Vejamos como gostaríamos que a nossa comunicação cliente-servidor aparecesse no mundo real. Apresento algo assim:

// on client side
public void Foo()
{
    var server = new MyServer(new Uri("https://www.myserver.com/");)
    int sum = server.Calculator.SumNumbers(12,13); 
}

// on server side
class CalculatorController : Controller{
    public int SumNumbers(int a, int b)
    {
        return a + b;
    }
}

Eu gostaria de ter total suporte ao Intellisense. Quando clico em servidor e . Quero que o Visual Studio mostre todos os controladores. E quando clico em CalculatorController e . Eu quero ver todos os métodos de ação. Eu também quero o melhor desempenho, carga de rede muito baixa e transferência de dados bidirecional. E preciso de um sistema confiável que lide com o controle de versão para poder implantar facilmente novas versões de clientes e novas versões de servidores.

É muito?

Observe que estou falando de APIs sem estado aqui . Isso é equivalente a um projeto C # em que existem apenas dois tipos de classes:

  • Classes estáticas apenas com métodos estáticos.
  • Classes POCO nas quais não há nada além de campos e propriedades cujo tipo é uma classe POCO primitiva ou outra.

Ter estado na API introduz complexidade, e essa é a raiz de todo mal. Portanto, pelo bem deste artigo, vamos tornar as coisas bonitas e sem estado.

Abordagem REST tradicional


A API REST apareceu no início dos anos 2000 e conquistou a Internet. Agora, essa é de longe a maneira mais popular de criar serviços da web.

O REST define um conjunto fixo de operações GET , POST , PUT e DELETE para solicitações do cliente para o servidor. Para cada solicitação, obtemos uma resposta contendo uma carga útil (geralmente JSON). As solicitações incluem parâmetros na própria solicitação ou como uma carga útil (geralmente JSON) quando é uma solicitação POST ou PUT.

Há um padrão de API RESTful que define as seguintes regras (das quais você realmente não precisa):

  • GET é usado para obter o recurso
  • PUT é usado para alterar o estado de um recurso.
  • POST é usado para criar um recurso.
  • DELETE é usado para excluir um recurso.

Se você não está familiarizado com o REST até agora, a explicação acima provavelmente não ajudará, então aqui está um exemplo. O .NET possui suporte REST interno. De fato, a API da Web do ASP.NET é por padrão um serviço da Web REST. Aqui está a aparência de um cliente e servidor ASP.NET típico:

No servidor:

[Route("People")]
public class PeopleController : Controller
{
    [HttpGet]
    public Person GetPersonById(int id)
    {
        Person person = _db.GetPerson(id);
        return person;//Automatically serialized to JSON
    }
} 

No cliente:

var client = new HttpClient();
string resultJson = await client.GetStringAsync("https://www.myserver.com/People/GetPersonById?id=123");
Person person = JsonConvert.DeserializeObject<Person>(resultJson);

O REST é extremamente conveniente, mas não adequado para o cenário ideal. Então, vamos ver se podemos fazer melhor.

Remontar


O ReFit não é uma alternativa ao REST. Em vez disso, ele é construído sobre o REST e nos permite chamar pontos de extremidade do servidor como se fossem um método simples. Isso é obtido compartilhando a interface entre o cliente e o servidor. No lado do servidor, seu controlador implementará a interface:

public interface IMyEmployeeApi
{
    [Get("/employee/{id}")]
    Task<Employee> GetEmployee(string id);
}

Em seguida, no lado do cliente, você precisará ativar a mesma interface e usar o seguinte código:


var api = RestService.For<IMyEmployeeApi>("https://www.myserver.com");
var employee = await api.GetEmployee("abc");

É tão simples. Não há necessidade de executar automação complexa ou usar ferramentas de terceiros, exceto alguns pacotes NuGet.

Isso está ficando muito mais próximo do cenário ideal. Agora, temos o IntelliSense e um contrato confiável entre cliente e servidor. Mas há outra opção que é ainda melhor em alguns aspectos.

Swagger


Como o ReFit, o Swagger também é construído sobre o REST. OpenAPI , ou Swagger , é uma especificação da API REST. Ele descreve um serviço da web REST em arquivos JSON simples. Esses arquivos são um esquema da API de serviço da web. Eles incluem:

  • Todos os caminhos (URLs) na API.
  • Operações esperadas (GET, POST, ...) para cada caminho. Cada caminho pode lidar com operações diferentes. Por exemplo, o mesmo caminho mystore.com/Product pode aceitar uma operação POST que adiciona um produto e uma operação GET que retorna um produto.
  • Os parâmetros esperados para cada caminho e operação.
  • Respostas esperadas para cada caminho.
  • Tipos de cada parâmetro e objeto de resposta.

Esse arquivo JSON é essencialmente um contrato entre clientes e o servidor. Aqui está um exemplo de arquivo swagger descrevendo um serviço da Web chamado Swagger Petstore (para maior clareza, removi algumas partes):

Esquema Json
{ 
   "swagger":"2.0",
   "info":{ 
      "version":"1.0.0",
      "title":"Swagger Petstore",
      "description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
   },
   "host":"petstore.swagger.io",
   "basePath":"/api",
   "schemes":[ 
      "http"
   ],
   "consumes":[ 
      "application/json"
   ],
   "produces":[ 
      "application/json"
   ],
   "paths":{ 
      "/pets":{ 
         "get":{ 
            "description":"Returns all pets from the system that the user has access to",
            "operationId":"findPets",
            "produces":[ 
               "application/json",
               "application/xml",
            ],
            "parameters":[ 
               { 
                  "name":"tags",
                  "in":"query",
                  "description":"tags to filter by",
                  "required":false,
                  "type":"array",
                  "items":{ 
                     "type":"string"
                  },
                  "collectionFormat":"csv"
               },
               { 
                  "name":"limit",
                  "in":"query",
                  "description":"maximum number of results to return",
                  "required":false,
                  "type":"integer",
                  "format":"int32"
               }
            ],
            "responses":{ 
               "200":{ 
                  "description":"pet response",
                  "schema":{ 
                     "type":"array",
                     "items":{ 
                        "$ref":"#/definitions/Pet"
                     }
                  }
               },
...
 


Vejamos as consequências disso. Usando um arquivo JSON como o acima, é possível criar um cliente C # com o IntelliSense completo. No final, você conhece todos os caminhos, operações, quais parâmetros eles esperam, que tipos de parâmetros, quais são as respostas.

Existem várias ferramentas que fazem exatamente isso. No lado do servidor, você pode usar o Swashbuckle.AspNetCore para adicionar o Swagger ao seu ASP.NET e criar os arquivos JSON especificados. Para o lado do cliente, você pode usar o swagger-codegen e o AutoRest para processar esses arquivos no formato JSON e gerar o cliente. Vamos ver um exemplo de como fazer isso:

Adicionando Swagger ao seu servidor ASP.NET


Comece adicionando o pacote NuGet Swashbuckle.AspNetCore . Em ConfigureServices , registre o gerador Swagger:

services.AddSwaggerGen(options => 
	options.SwaggerDoc("v1", new OpenApiInfo {Title = "My Web API", Version = "v1"}));

No arquivo Startup.cs no método Configure , adicione:

app.UseSwagger();

Finalmente, os métodos de ação dentro do controlador devem ser marcados com os atributos [HttpXXX] e [FromXXX] :

[HttpPost]
public async Task AddEmployee([FromBody]Employee employee)
{
    //...
}
 
[HttpGet]
public async Task<Employee> Employee([FromQuery]string id)
{
    //...
}

É tão simples para o lado do servidor. Quando o projeto for iniciado , será gerado um arquivo swagger.json , que você poderá usar para gerar o cliente.

Gerando um cliente a partir do Swagger usando o AutoRest


Para começar a usar o AutoRest , instale-o com npm : npm install -g autorest . Após a instalação, você precisará usar a interface da linha de comandos do AutoRest para criar o cliente C # a partir do arquivo swagger.json . Aqui está um exemplo:

autorest --input-file="./swagger.json" --output-folder="GeneratedClient" --namespace="MyClient" --override-client-name="MyClient" --csharp

Isso criará uma pasta GeneratedClient com os arquivos C # gerados. Observe que o espaço para nome e o nome do cliente são redefinidos. Adicione esta pasta ao seu projeto de cliente no Visual Studio, como mostrado abaixo.



Você precisará instalar o pacote Microsoft.Rest.ClientRuntime NuGet, porque o código gerado depende dele. Após a instalação, você pode usar a API como uma classe C # regular:

var client = new MyClient();
Employee employee = client.Employee(id: "abc");

Existem algumas sutilezas que você pode ler na documentação do AutoRest. E você precisará automatizar esse processo, então sugiro ler o manual de Patrick Svensson para obter algumas dicas úteis, bem como este artigo de Peter Yausovets.

Meu problema com o Swagger é que o arquivo JSON é criado em tempo de execução, o que dificulta um pouco a automatização do processo de CI / CD.

REST tradicional vs Swagger vs ReFit


Aqui estão alguns pontos a considerar ao escolher.

  • Se você possui uma API REST privada muito simples, pode não precisar se preocupar com a geração de clientes e interfaces comuns. Uma pequena tarefa não justifica um esforço adicional.
  • O Swagger suporta muitos idiomas e o ReFit suporta apenas .NET. O Swagger também é a base de muitas ferramentas, testes, ferramentas de automação e ferramentas de interface do usuário. Essa provavelmente será a melhor opção se você estiver criando uma API pública grande.
  • O Swagger é muito mais complicado que o ReFit. Com o ReFit, basta adicionar uma única interface ao projeto do servidor e do cliente. Por outro lado, com o ReFit, você terá que criar novas interfaces para cada controlador, enquanto o Swagger cuidará disso automaticamente.

Mas antes de decidir algo, confira a quarta opção, que não tem nada a ver com o REST.

gRPC


O gRPC (gRPC - Remote Procedure Call) é um sistema de chamada de procedimento remoto de código aberto desenvolvido pelo Google. Isso é um pouco como o REST, no sentido em que fornece uma maneira de enviar solicitações do cliente para o servidor. Mas isso é muito diferente, aqui estão as semelhanças e diferenças:

  • Como o REST, o gRPC é independente do idioma. Existem ferramentas para todos os idiomas populares, incluindo C #.
  • O gRPC é baseado em contrato e usa arquivos .proto para definir o contrato. Isso é um pouco semelhante ao Swagger swagger.json e à interface comum do ReFit. Um cliente de qualquer linguagem de programação pode ser gerado a partir desses arquivos.
  • gRPC Protocol Buffer (Protobuf). REST, JSON XML. , , .
  • gRPC HTTP/2. . , REST HTTP 1.x ( HTTP 1.1).
  • HTTP 1.1 TCP- () , HTTP/2 .
  • HTTP/2 . , TCP- . , , HTTP 1.1.
  • gRPC .

Existem duas maneiras de usar o gRPC. Para o .NET Core 3.0, existe uma biblioteca gRPC para .NET totalmente gerenciada . Além disso, você pode usar o gRPC C # . Isso não significa que o gRPC para .NET substitua o gRPC C # . Vamos ver um exemplo com o gRPC for .NET mais recente .

Lado do servidor GRPC para .NET


Este não é um guia, mas uma ideia geral do que esperar. Aqui está como seria um exemplo de controlador no gRPC:


public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request,
        ServerCallContext context)
    {
        _logger.LogInformation("Saying hello to {Name}", request.Name);
        return Task.FromResult(new HelloReply 
        {
            Message = "Hello " + request.Name
        });
    }
}

Você precisa adicionar o seguinte ao Configure no arquivo Startup.cs :


app.UseEndpoints(endpoints =>
{
    endpoints.MapGrpcService<GreeterService>();
});

A API é descrita no arquivo .proto , que faz parte do projeto:

syntax = "proto3";
 
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
 
message HelloRequest {
  string name = 1;
}
 
message HelloReply {
  string message = 1;
}

Este arquivo .proto é adicionado ao arquivo .csproj:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto"/>
</ItemGroup>

GRPC para .NET lado do cliente


O cliente é gerado a partir de arquivos .proto . O código em si é muito simples:


var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
 
var response = await client.SayHello(
    new HelloRequest { Name = "World" });
 
Console.WriteLine(response.Message);

gRPC vs REST


GRPC parece um bom negócio. É mais rápido e fácil sob o capô. Então, todos devemos mudar de REST para gRPC? A resposta é que depende . Aqui estão algumas considerações:

Na minha experiência, o trabalho com o gRPC e o ASP.NET ainda é pequeno. Você estará melhor com o suporte REST maduro. No que diz respeito à comunicação contratual, é bom, exceto que você tem alternativas semelhantes no REST das quais já falamos: Swagger e ReFit.

A maior vantagem é o desempenho. Na maioria dos casos, de acordo com esses critérios , o gRPC é muito mais rápido. Especialmente para grandes cargas úteis para as quais a serialização do Protobuf realmente importa. Isso significa que essa é uma grande vantagem para um servidor com uma carga alta.

Mover do REST para o gRPC em um aplicativo ASP.NET grande será difícil. No entanto, se você tiver uma arquitetura baseada em microsserviço, essa transição ficará muito mais fácil de ser concluída gradualmente.

Outras formas de comunicação


Existem várias outras formas de comunicação que eu não mencionei, mas vale a pena saber que elas existem:

  • GraphQL é uma linguagem de consulta API desenvolvida pelo Facebook. Isso permite que o cliente solicite exatamente os dados necessários ao servidor. Assim, você pode criar apenas um terminal no servidor, que será extremamente flexível e retornará apenas os dados necessários ao cliente. O GraphQL se tornou muito popular nos últimos anos.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • O WCF é uma tecnologia antiga que usa principalmente a comunicação entre processos baseada em SOAP. Essa é uma estrutura enorme em que não vou entrar, só posso dizer que perdeu a popularidade das cargas úteis REST e JSON.

Isso é tudo. Espero que o artigo tenha sido interessante! Bom código para todos!

All Articles