La relación entre C # y C #: REST, gRPC y todo lo demás

Hay muchas formas de comunicarse entre un cliente C # y un servidor C #. Algunos de ellos son confiables, otros no. Algunos son muy rápidos, otros no. Es importante conocer las diferentes opciones para que pueda decidir cuál es mejor para usted. Este artículo discutirá las tecnologías más populares hasta la fecha y por qué son tan ampliamente utilizadas. Hablaremos sobre REST, gRPC y todo lo demás.

Escenario óptimo


Veamos cómo nos gustaría que se viera nuestra comunicación cliente-servidor en el mundo real. Os presento algo como esto:

// 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;
    }
}

Me gustaría tener soporte completo de Intellisense. Cuando hago clic en servidor y . Quiero que Visual Studio muestre todos los controladores. Y cuando hago clic en CalculatorController y . Quiero ver todos los métodos de acción. También quiero el más alto rendimiento, muy poca carga de red y transferencia de datos bidireccional. Y necesito un sistema confiable que haga frente al control de versiones para que pueda implementar fácilmente nuevas versiones de clientes y nuevas versiones de servidores.

¿Es demasiado?

Tenga en cuenta que estoy hablando de API sin estado aquí . Esto es equivalente a un proyecto de C # donde solo hay dos tipos de clases:

  • Clases estáticas solo con métodos estáticos.
  • Clases de POCO en las que no hay nada más que campos y propiedades cuyo tipo es una primitiva u otra clase de POCO.

Tener estado en la API introduce complejidad, y esta es la raíz de todo mal. Entonces, por el bien de este artículo, hagamos las cosas bellas y apátridas.

Enfoque REST tradicional


REST API apareció a principios de la década de 2000 y conquistó Internet. Ahora esta es, con mucho, la forma más popular de crear servicios web.

REST define un conjunto fijo de operaciones GET , POST , PUT y DELETE para solicitudes del cliente al servidor. Para cada solicitud, obtenemos una respuesta que contiene una carga útil (generalmente JSON). Las solicitudes incluyen parámetros en la solicitud misma o como una carga útil (generalmente JSON) cuando se trata de una solicitud POST o PUT.

Existe un estándar API RESTful que define las siguientes reglas (que realmente no necesita):

  • GET se usa para obtener el recurso
  • PUT se usa para cambiar el estado de un recurso.
  • POST se usa para crear un recurso.
  • DELETE se usa para eliminar un recurso.

Si no está familiarizado con REST hasta ahora, la explicación anterior probablemente no ayudará, así que aquí hay un ejemplo. .NET tiene soporte REST incorporado. De hecho, la API web ASP.NET es por defecto un servicio web REST. Así es como se ve un cliente y servidor ASP.NET típico:

En el servidor:

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

En el cliente:

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

REST es muy conveniente, pero no es adecuado para el escenario óptimo. Entonces, veamos si podemos hacerlo mejor.

Repararse


ReFit no es una alternativa a REST. En cambio, está construido sobre REST y nos permite invocar los puntos finales del servidor como si fueran un método simple. Esto se logra compartiendo la interfaz entre el cliente y el servidor. En el lado del servidor, su controlador implementará la interfaz:

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

Luego, en el lado del cliente, deberá habilitar la misma interfaz y usar el siguiente código:


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

Es muy simple No es necesario ejecutar una automatización compleja o utilizar herramientas de terceros, excepto un par de paquetes NuGet.

Esto se está acercando mucho al escenario óptimo. Ahora tenemos IntelliSense y un contrato confiable entre cliente y servidor. Pero hay otra opción que es aún mejor en algunos aspectos.

Pavonearse


Al igual que ReFit, Swagger también está construido sobre REST. OpenAPI , o Swagger , es una especificación de API REST. Describe un servicio web REST en archivos JSON simples. Estos archivos son un esquema de API de servicio web. Incluyen:

  • Todas las rutas (URL) en la API.
  • Operaciones esperadas (GET, POST, ...) para cada ruta. Cada ruta puede manejar diferentes operaciones. Por ejemplo, la misma ruta mystore.com/Product puede aceptar una operación POST que agrega un producto y una operación GET que devuelve un producto.
  • Los parámetros esperados para cada ruta y operación.
  • Respuestas esperadas para cada ruta.
  • Tipos de cada parámetro y objeto de respuesta.

Este archivo JSON es esencialmente un contrato entre clientes y el servidor. Aquí hay un ejemplo de archivo swagger que describe un servicio web llamado Swagger Petstore (para mayor claridad, eliminé algunas 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"
                     }
                  }
               },
...
 


Veamos las consecuencias de esto. Con un archivo JSON como el anterior, puede crear un cliente C # con IntelliSense completo. Al final, conoce todas las rutas, operaciones, qué parámetros esperan, qué tipos de parámetros, cuáles son las respuestas.

Hay varias herramientas que hacen justamente eso. En el lado del servidor, puede usar Swashbuckle.AspNetCore para agregar Swagger a su ASP.NET y crear los archivos JSON especificados. Para el lado del cliente, puede usar swagger-codegen y AutoRest para procesar estos archivos en formato JSON y generar el cliente. Veamos un ejemplo de cómo hacer esto:

Agregar Swagger a su servidor ASP.NET


Comience agregando el paquete NuGet Swashbuckle.AspNetCore . En ConfigureServices , registre el generador Swagger:

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

En el archivo Startup.cs en el método Configure , agregue:

app.UseSwagger();

Finalmente, los métodos de acción dentro del controlador deben marcarse con los atributos [HttpXXX] y [FromXXX] :

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

Es muy simple para el lado del servidor. Cuando se inicia el proyecto , se generará un archivo swagger.json , que puede usar para generar el cliente.

Generando un cliente de Swagger usando AutoRest


Para comenzar a usar AutoRest , instálelo con npm : npm install -g autorest . Después de la instalación, deberá usar la interfaz de línea de comandos de AutoRest para crear el cliente C # a partir del archivo swagger.json . Aquí hay un ejemplo:

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

Esto creará una carpeta GeneratedClient con los archivos C # generados. Tenga en cuenta que el espacio de nombres y el nombre del cliente se redefinen. Agregue esta carpeta a su proyecto de cliente en Visual Studio como se muestra a continuación.



Deberá instalar el paquete Microsoft.Rest.ClientRuntime NuGet, porque el código generado depende de él. Después de la instalación, puede usar la API como una clase de C # normal:

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

Hay algunas sutilezas sobre las que puede leer en la documentación de AutoRest. Y necesitará automatizar este proceso, por lo que le sugiero que lea el manual de Patrick Svensson para obtener algunos consejos útiles, así como este artículo de Peter Yausovets.

Mi problema con Swagger es que el archivo JSON se crea en tiempo de ejecución, por lo que esto dificulta un poco la automatización del proceso de CI / CD.

REST tradicional vs Swagger vs ReFit


Aquí hay algunos puntos a considerar al elegir.

  • Si tiene una API REST privada muy simple, es posible que no tenga que preocuparse por la generación de clientes y las interfaces comunes. Una tarea pequeña no justifica un esfuerzo adicional.
  • Swagger admite muchos idiomas, y ReFit solo admite .NET. Swagger también es la base de muchas herramientas, pruebas, herramientas de automatización y herramientas de interfaz de usuario. Esta será probablemente la mejor opción si está creando una gran API pública.
  • Swagger es mucho más complicado que ReFit. Con ReFit, es solo cuestión de agregar una sola interfaz tanto a su servidor como a su proyecto de cliente. Por otro lado, con ReFit tendrás que crear nuevas interfaces para cada controlador, mientras que Swagger se encargará de esto automáticamente.

Pero antes de decidir algo, consulte la cuarta opción, que no tiene nada que ver con REST.

gRPC


gRPC (gRPC - Remote Procedure Call) es un sistema de llamada a procedimiento remoto de código abierto desarrollado por Google. Esto es un poco como REST en el sentido de que proporciona una forma de enviar solicitudes del cliente al servidor. Pero esto es muy diferente, aquí están las similitudes y diferencias:

  • Al igual que REST, gRPC es independiente del lenguaje. Existen herramientas para todos los lenguajes populares, incluido C #.
  • gRPC se basa en contratos y utiliza archivos .proto para definir el contrato. Esto es algo similar a Swagger swagger.json y la interfaz común ReFit. Se puede generar un cliente de cualquier lenguaje de programación a partir de estos archivos.
  • 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 .

Hay dos formas de usar gRPC. Para .NET Core 3.0, hay un gRPC completamente administrado para la biblioteca .NET . También puede usar gRPC C # . Esto no significa que gRPC para .NET reemplace gRPC C # . Veamos un ejemplo con el nuevo gRPC para .NET .

GRPC para el lado del servidor .NET


Esto no es una guía, sino más bien una idea general de qué esperar. Así es como se vería un controlador de ejemplo en 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
        });
    }
}

Debe agregar lo siguiente para Configurar en el archivo Startup.cs :


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

La API se describe en el archivo .proto , que forma parte del proyecto:

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

Este archivo .proto se agrega en el archivo .csproj:

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

GRPC para el lado del cliente .NET


El cliente se genera a partir de archivos .proto . El código en sí es muy simple:


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 suena como un buen negocio. Es más rápido y fácil debajo del capó. Entonces, ¿deberíamos cambiar de REST a gRPC? La respuesta es que depende . Aquí hay algunas consideraciones:

en mi experiencia, trabajar con gRPC y ASP.NET todavía es pequeño. Estará mejor con un soporte REST maduro. En lo que respecta a la comunicación del contrato, esto es bueno, excepto que tiene alternativas similares a REST de las que ya hemos hablado: Swagger y ReFit.

La mayor ventaja es el rendimiento. En la mayoría de los casos, según estos criterios , el gRPC es mucho más rápido. Especialmente para grandes cargas útiles para las cuales la serialización Protobuf realmente importa. Esto significa que esta es una gran ventaja para un servidor con una gran carga.

Pasar de REST a gRPC en una gran aplicación ASP.NET será difícil. Sin embargo, si tiene una arquitectura basada en microservicios, esta transición será mucho más fácil de completar gradualmente.

Otras formas de comunicación


Hay varias otras formas de comunicación que no mencioné en absoluto, pero vale la pena saber que existen:

  • GraphQL es un lenguaje de consulta API desarrollado por Facebook. Esto permite al cliente solicitar exactamente los datos que necesita del servidor. Por lo tanto, puede crear solo un punto final en el servidor, que será extremadamente flexible y devolverá solo los datos que el cliente necesita. GraphQL se ha vuelto muy popular en los últimos años.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF es una tecnología antigua que utiliza principalmente la comunicación entre procesos basada en SOAP. Este es un gran marco en el que no entraré, solo puedo decir que ha perdido su popularidad para las cargas útiles REST y JSON.

Eso es todo. ¡Espero que el artículo haya sido interesante! Buen código para todos!

All Articles