La relation entre C # et C #: REST, gRPC et tout le reste

Il existe de nombreuses façons de communiquer entre un client C # et un serveur C #. Certains d'entre eux sont fiables, d'autres non. Certains sont très rapides, d'autres non. Il est important de connaître les différentes options afin de pouvoir décider laquelle vous convient le mieux. Cet article discutera des technologies les plus populaires à ce jour et pourquoi elles sont si largement utilisées. Nous parlerons de REST, gRPC et tout le reste.

Scénario optimal


Voyons comment nous aimerions que notre communication client-serveur se présente dans le monde réel. Je présente quelque chose comme ça:

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

J'aimerais avoir un support Intellisense complet. Lorsque je clique sur serveur et . Je veux que Visual Studio affiche tous les contrôleurs. Et quand je clique sur CalculatorController et . Je veux voir toutes les méthodes d'action. Je veux également les performances les plus élevées, une charge réseau très faible et un transfert de données bidirectionnel. Et j'ai besoin d'un système fiable qui gère le contrôle de version afin que je puisse facilement déployer de nouvelles versions de clients et de nouvelles versions de serveurs.

C'est trop?

Notez que je parle ici des API sans état . Cela équivaut à un projet C # où il n'y a que deux types de classes:

  • Classes statiques avec méthodes statiques uniquement.
  • Classes POCO dans lesquelles il n'y a que des champs et des propriétés dont le type est une classe POCO primitive ou autre.

Avoir un état dans l'API introduit de la complexité, et c'est la racine de tout mal. Donc, pour le bien de cet article, rendons les choses belles et apatrides.

Approche REST traditionnelle


L'API REST est apparue au début des années 2000 et a conquis Internet. Maintenant, c'est de loin le moyen le plus populaire pour créer des services Web.

REST définit un ensemble fixe d'opérations GET , POST , PUT et DELETE pour les demandes du client au serveur. Pour chaque demande, nous obtenons une réponse contenant une charge utile (généralement JSON). Les demandes incluent des paramètres dans la demande elle-même ou en tant que charge utile (généralement JSON) lorsqu'il s'agit d'une demande POST ou PUT.

Il existe une norme API RESTful qui définit les règles suivantes (dont vous n'avez pas vraiment besoin):

  • GET est utilisé pour obtenir la ressource
  • PUT est utilisé pour changer l'état d'une ressource.
  • POST est utilisé pour créer une ressource.
  • DELETE est utilisé pour supprimer une ressource.

Si vous n'êtes pas familier avec REST jusqu'à présent, l'explication ci-dessus n'aidera probablement pas, alors voici un exemple. .NET a un support REST intégré. En fait, l'API Web ASP.NET est par défaut un service Web REST. Voici à quoi ressemble un client et un serveur ASP.NET typiques:

Sur le serveur:

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

Sur le client:

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

REST est sacrément pratique, mais ne convient pas au scénario optimal. Voyons donc si nous pouvons le faire mieux.

Remonter


ReFit n'est pas une alternative à REST. Au lieu de cela, il est construit au-dessus de REST et nous permet d'appeler des points de terminaison de serveur comme s'il s'agissait d'une méthode simple. Ceci est réalisé en partageant l'interface entre le client et le serveur. Côté serveur, votre contrôleur implémentera l'interface:

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

Ensuite, côté client, vous devrez activer la même interface et utiliser le code suivant:


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

C'est tellement simple. Il n'est pas nécessaire d'exécuter une automatisation complexe ou d'utiliser des outils tiers, à l'exception de quelques packages NuGet.

Cela se rapproche beaucoup du scénario optimal. Nous avons maintenant IntelliSense et un contrat fiable entre le client et le serveur. Mais il existe une autre option qui est encore meilleure à certains égards.

Swagger


Comme ReFit, Swagger est également construit au-dessus de REST. OpenAPI , ou Swagger , est une spécification d'API REST. Il décrit un service Web REST dans des fichiers JSON simples. Ces fichiers sont un schéma d'API de service Web. Ils comprennent:

  • Tous les chemins (URL) dans l'API.
  • Opérations attendues (GET, POST, ...) pour chaque chemin. Chaque chemin peut gérer différentes opérations. Par exemple, le même chemin mystore.com/Product peut accepter une opération POST qui ajoute un produit et une opération GET qui renvoie un produit.
  • Les paramètres attendus pour chaque chemin et opération.
  • Réponses attendues pour chaque chemin.
  • Types de chaque paramètre et objet de réponse.

Ce fichier JSON est essentiellement un contrat entre les clients et le serveur. Voici un exemple de fichier swagger décrivant un service Web appelé Swagger Petstore (pour plus de clarté, j'ai supprimé certaines parties):

Schéma 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"
                     }
                  }
               },
...
 


Voyons les conséquences de cela. En utilisant un fichier JSON comme celui ci-dessus, vous pouvez potentiellement créer un client C # avec IntelliSense complet. Au final, vous connaissez tous les chemins, opérations, quels paramètres ils attendent, quels types de paramètres, quelles sont les réponses.

Il existe plusieurs outils pour cela. Côté serveur, vous pouvez utiliser Swashbuckle.AspNetCore pour ajouter Swagger à votre ASP.NET et créer les fichiers JSON spécifiés. Côté client, vous pouvez utiliser swagger-codegen et AutoRest pour traiter ces fichiers au format JSON et générer le client. Voyons un exemple de la façon de procéder:

Ajout de Swagger à votre serveur ASP.NET


Commencez par ajouter le package NuGet Swashbuckle.AspNetCore . Dans ConfigureServices , enregistrez le générateur Swagger:

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

Dans le fichier Startup.cs de la méthode Configure , ajoutez:

app.UseSwagger();

Enfin, les méthodes d'action à l'intérieur du contrôleur doivent être marquées avec les attributs [HttpXXX] et [FromXXX] :

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

C'est tellement simple côté serveur. Lorsque le projet démarre , un fichier swagger.json est généré , que vous pouvez utiliser pour générer le client.

Génération d'un client à partir de Swagger à l'aide d'AutoRest


Pour commencer à utiliser AutoRest , installez-le avec npm : npm install -g autorest . Après l'installation, vous devrez utiliser l'interface de ligne de commande AutoRest pour créer le client C # à partir du fichier swagger.json . Voici un exemple:

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

Cela créera un dossier GeneratedClient avec les fichiers C # générés. Notez que l'espace de noms et le nom du client sont redéfinis. Ajoutez ce dossier à votre projet client dans Visual Studio comme indiqué ci-dessous.



Vous devrez installer le package NuGet Microsoft.Rest.ClientRuntime , car le code généré en dépend. Après l'installation, vous pouvez utiliser l'API comme une classe C # régulière:

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

Il existe certaines subtilités que vous pouvez lire dans la documentation d' AutoRest. Et vous devrez automatiser ce processus, donc je vous suggère de lire le manuel de Patrick Svensson pour quelques conseils utiles, ainsi que cet article de Peter Yausovets.

Mon problème avec Swagger est que le fichier JSON est créé au moment de l'exécution, donc cela rend un peu difficile l'automatisation du processus CI / CD.

REST traditionnel vs Swagger vs ReFit


Voici quelques points à considérer lors du choix.

  • Si vous disposez d'une API REST privée très simple, vous n'aurez peut-être pas à vous soucier de la génération de clients et des interfaces communes. Une petite tâche ne justifie pas un effort supplémentaire.
  • Swagger prend en charge de nombreuses langues et ReFit ne prend en charge que .NET. Swagger est également la base de nombreux outils, tests, outils d'automatisation et outils d'interface utilisateur. Ce sera probablement le meilleur choix si vous créez une grande API publique.
  • Swagger est beaucoup plus compliqué que ReFit. Avec ReFit, il suffit d'ajouter une interface unique à la fois à votre projet serveur et client. En revanche, avec ReFit, vous devrez créer de nouvelles interfaces pour chaque contrôleur, tandis que Swagger s'en chargera automatiquement.

Mais avant de décider quelque chose, consultez la 4ème option, qui n'a rien à voir avec REST.

gRPC


gRPC (gRPC - Remote Procedure Call) est un système d'appel de procédure à distance open source développé par Google. C'est un peu comme REST dans le sens où il fournit un moyen d'envoyer des requêtes du client au serveur. Mais c'est très différent, voici les similitudes et les différences:

  • Comme REST, gRPC est indépendant du langage. Il existe des outils pour tous les langages populaires, y compris C #.
  • gRPC est basé sur un contrat et utilise des fichiers .proto pour définir le contrat. Ceci est quelque peu similaire à Swagger swagger.json et à l'interface ReFit commune. Un client de n'importe quel langage de programmation peut être généré à partir de ces fichiers.
  • 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 .

Il existe deux façons d'utiliser gRPC. Pour .NET Core 3.0, il existe une bibliothèque gRPC pour .NET entièrement gérée . Vous pouvez également utiliser gRPC C # . Cela ne signifie pas que gRPC pour .NET remplace gRPC C # . Voyons un exemple avec le plus récent gRPC pour .NET .

Côté serveur GRPC pour .NET


Ce n'est pas un guide, mais plutôt une idée générale de ce à quoi s'attendre. Voici à quoi ressemblerait un exemple de contrôleur dans 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
        });
    }
}

Vous devez ajouter ce qui suit à Configure dans le fichier Startup.cs :


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

L'API est décrite dans le fichier .proto , qui fait partie du projet:

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

Ce fichier .proto est ajouté dans le fichier .csproj:

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

Côté client GRPC pour .NET


Le client est généré à partir de fichiers .proto . Le code lui-même est très 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 sonne comme une bonne affaire. C'est plus rapide et plus facile sous le capot. Alors, devrions-nous tous passer de REST à gRPC? La réponse est que cela dépend . Voici quelques considérations:

D'après mon expérience, travailler avec gRPC et ASP.NET est encore petit. Vous serez mieux avec un support REST mature. En ce qui concerne la communication contractuelle, c'est bien, sauf que vous avez des alternatives similaires à REST dont nous avons déjà parlé: Swagger et ReFit.

Le plus grand avantage est la performance. Dans la plupart des cas, selon ces critères , le gRPC est beaucoup plus rapide. Surtout pour les grandes charges utiles pour lesquelles la sérialisation Protobuf est vraiment importante. Cela signifie que c'est un énorme avantage pour un serveur avec une charge élevée.

Passer de REST à gRPC dans une grande application ASP.NET sera difficile. Cependant, si vous avez une architecture basée sur des microservices, cette transition deviendra beaucoup plus facile à effectuer progressivement.

Autres moyens de communication


Il existe plusieurs autres modes de communication que je n'ai pas du tout mentionnés, mais il faut savoir qu'ils existent:

  • GraphQL est un langage de requête API développé par Facebook. Cela permet au client de demander exactement les données dont il a besoin au serveur. Ainsi, vous ne pouvez créer qu'un seul point de terminaison sur le serveur, ce qui sera extrêmement flexible et ne renverra que les données dont le client a besoin. GraphQL est devenu très populaire ces dernières années.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF est une ancienne technologie qui utilise principalement la communication inter-processus basée sur SOAP. Il s'agit d'un cadre énorme dans lequel je n'entrerai pas, je peux seulement dire qu'il a perdu sa popularité pour les charges utiles REST et JSON.

C'est tout. J'espère que l'article était intéressant! Bon code à tous!

All Articles