Die Beziehung zwischen C # und C #: REST, gRPC und alles dazwischen

Es gibt viele Möglichkeiten, zwischen einem C # -Client und einem C # -Server zu kommunizieren. Einige von ihnen sind zuverlässig, andere nicht. Einige sind sehr schnell, andere nicht. Es ist wichtig, die verschiedenen Optionen zu kennen, damit Sie entscheiden können, welche für Sie am besten geeignet ist. In diesem Artikel werden die bislang beliebtesten Technologien und ihre Verbreitung erläutert. Wir werden über REST, gRPC und alles dazwischen sprechen.

Optimales Szenario


Schauen wir uns an, wie unsere Client-Server-Kommunikation in der realen Welt aussehen soll. Ich präsentiere so etwas:

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

Ich hätte gerne volle Intellisense-Unterstützung. Wenn ich auf Server und klicke . Ich möchte, dass Visual Studio alle Controller anzeigt. Und wenn ich auf CalculatorController und klicke . Ich möchte alle Aktionsmethoden sehen. Ich möchte auch die höchste Leistung, eine sehr geringe Netzwerklast und eine bidirektionale Datenübertragung. Und ich brauche ein zuverlässiges System, das mit der Versionskontrolle fertig wird, damit ich problemlos neue Versionen von Clients und neue Versionen von Servern bereitstellen kann.

Es ist zu viel?

Beachten Sie, dass ich hier über zustandslose APIs spreche . Dies entspricht einem C # -Projekt, bei dem es nur zwei Arten von Klassen gibt:

  • Statische Klassen nur mit statischen Methoden.
  • POCO-Klassen, in denen es nur Felder und Eigenschaften gibt, deren Typ eine primitive oder eine andere POCO-Klasse ist.

Der Status in der API führt zu Komplexität, und dies ist die Wurzel allen Übels. Lassen Sie uns für diesen Artikel die Dinge schön und staatenlos machen.

Traditioneller REST-Ansatz


Die REST-API erschien Anfang der 2000er Jahre und eroberte das Internet. Dies ist bei weitem die beliebteste Methode zum Erstellen von Webdiensten.

REST definiert einen festen Satz von GET- , POST- , PUT- und DELETE- Operationen für Anforderungen vom Client an den Server. Für jede Anfrage erhalten wir eine Antwort mit einer Nutzlast (normalerweise JSON). Anforderungen enthalten Parameter in der Anforderung selbst oder als Nutzlast (normalerweise JSON), wenn es sich um eine POST- oder PUT-Anforderung handelt.

Es gibt einen RESTful-API- Standard , der die folgenden Regeln definiert (die Sie nicht wirklich benötigen):

  • GET wird verwendet, um die Ressource abzurufen
  • PUT wird verwendet, um den Status einer Ressource zu ändern.
  • POST wird zum Erstellen einer Ressource verwendet.
  • DELETE wird verwendet, um eine Ressource zu löschen.

Wenn Sie mit REST bisher nicht vertraut sind, hilft die obige Erklärung wahrscheinlich nicht weiter. Hier ist ein Beispiel. .NET verfügt über eine integrierte REST-Unterstützung. Tatsächlich ist die ASP.NET-Web-API standardmäßig ein REST-Webdienst. So sieht ein typischer ASP.NET-Client und -Server aus:

Auf dem Server:

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

Auf dem 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 ist verdammt praktisch, aber nicht für das optimale Szenario geeignet. Mal sehen, ob wir es besser machen können.

Überholen


ReFit ist keine Alternative zu REST. Stattdessen basiert es auf REST und ermöglicht es uns, Serverendpunkte aufzurufen, als ob sie eine einfache Methode wären. Dies wird erreicht, indem die Schnittstelle zwischen Client und Server gemeinsam genutzt wird. Auf der Serverseite implementiert Ihr Controller die Schnittstelle:

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

Dann müssen Sie auf der Clientseite dieselbe Schnittstelle aktivieren und den folgenden Code verwenden:


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

Es ist so einfach. Mit Ausnahme einiger NuGet-Pakete ist es nicht erforderlich, eine komplexe Automatisierung auszuführen oder Tools von Drittanbietern zu verwenden.

Dies kommt dem optimalen Szenario viel näher. Jetzt haben wir IntelliSense und einen zuverlässigen Vertrag zwischen Client und Server. Aber es gibt noch eine andere Option, die in mancher Hinsicht noch besser ist.

Stolzieren


Wie ReFit basiert auch Swagger auf REST. OpenAPI oder Swagger ist eine REST-API-Spezifikation. Es beschreibt einen REST-Webdienst in einfachen JSON-Dateien. Diese Dateien sind ein Webdienst-API-Schema. Sie beinhalten:

  • Alle Pfade (URLs) in der API.
  • Erwartete Operationen (GET, POST, ...) für jeden Pfad. Jeder Pfad kann unterschiedliche Operationen ausführen. Der gleiche Pfad mystore.com/Product kann beispielsweise eine POST-Operation akzeptieren, die ein Produkt hinzufügt, und eine GET-Operation, die ein Produkt zurückgibt.
  • Die erwarteten Parameter für jeden Pfad und jede Operation.
  • Erwartete Antworten für jeden Pfad.
  • Typen der einzelnen Parameter und Antwortobjekte.

Diese JSON-Datei ist im Wesentlichen ein Vertrag zwischen Clients und dem Server. Hier ist eine Beispiel-Swagger-Datei, die einen Webdienst namens Swagger Petstore beschreibt (aus Gründen der Übersichtlichkeit habe ich einige Teile entfernt):

Json-Schema
{ 
   "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"
                     }
                  }
               },
...
 


Schauen wir uns die Konsequenzen an. Mit einer JSON-Datei wie der oben genannten können Sie möglicherweise einen C # -Client mit vollständigem IntelliSense erstellen. Am Ende kennen Sie alle Pfade, Operationen, welche Parameter sie erwarten, welche Arten von Parametern, was sind die Antworten.

Es gibt mehrere Tools, die genau das tun. Auf der Serverseite können Sie Swashbuckle.AspNetCore verwenden , um Swagger zu Ihrem ASP.NET hinzuzufügen und die angegebenen JSON-Dateien zu erstellen. Auf der Clientseite können Sie diese Dateien im JSON-Format mit swagger-codegen und AutoRest verarbeiten und den Client generieren. Sehen wir uns ein Beispiel dafür an:

Hinzufügen von Swagger zu Ihrem ASP.NET-Server


Fügen Sie zunächst das NuGet Swashbuckle.AspNetCore- Paket hinzu . Registrieren Sie in ConfigureServices den Swagger-Generator:

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

Fügen Sie in der Datei Startup.cs in der Configure- Methode Folgendes hinzu:

app.UseSwagger();

Schließlich müssen die Aktionsmethoden im Controller mit den Attributen [HttpXXX] und [FromXXX] gekennzeichnet sein :

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

Für den Server ist das so einfach. Wenn das Projekt gestartet wird , wird eine swagger.json- Datei generiert , mit der Sie den Client generieren können.

Generieren eines Clients aus Swagger mit AutoRest


Um AutoRest zu verwenden , installieren Sie es mit npm : npm install -g autorest . Nach der Installation müssen Sie die AutoRest-Befehlszeilenschnittstelle verwenden, um den C # -Client aus der Datei swagger.json zu erstellen . Hier ist ein Beispiel:

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

Dadurch wird ein GeneratedClient- Ordner mit den generierten C # -Dateien erstellt. Beachten Sie, dass der Namespace und der Clientname neu definiert werden. Fügen Sie diesen Ordner wie unten gezeigt zu Ihrem Client-Projekt in Visual Studio hinzu.



Sie müssen das Microsoft.Rest.ClientRuntime NuGet-Paket installieren , da der generierte Code davon abhängt. Nach der Installation können Sie die API wie eine normale C # -Klasse verwenden:

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

Es gibt einige Feinheiten, über die Sie in der AutoRest- Dokumentation lesen können . Und Sie müssen diesen Prozess automatisieren. Ich empfehle daher, das Handbuch von Patrick Svensson zu lesen, um einige nützliche Tipps sowie diesen Artikel von Peter Yausovets zu erhalten.

Mein Problem mit Swagger ist, dass die JSON-Datei zur Laufzeit erstellt wird, was die Automatisierung des CI / CD-Prozesses etwas erschwert.

Traditionelle REST vs Swagger vs ReFit


Hier sind einige Punkte, die bei der Auswahl zu beachten sind.

  • Wenn Sie über eine sehr einfache private REST-API verfügen, müssen Sie sich möglicherweise nicht um die Clientgenerierung und die allgemeinen Schnittstellen kümmern. Eine kleine Aufgabe rechtfertigt keinen zusätzlichen Aufwand.
  • Swagger unterstützt viele Sprachen und ReFit unterstützt nur .NET. Swagger ist auch die Basis für viele Tools, Tests, Automatisierungstools und Tools für die Benutzeroberfläche. Dies ist wahrscheinlich die beste Wahl, wenn Sie eine große öffentliche API erstellen.
  • Swagger ist viel komplizierter als ReFit. Mit ReFit müssen Sie lediglich Ihrem Server- und Client-Projekt eine einzige Schnittstelle hinzufügen. Andererseits müssen Sie mit ReFit neue Schnittstellen für jeden Controller erstellen, während Swagger sich automatisch darum kümmert.

Bevor Sie sich jedoch für etwas entscheiden, sollten Sie die vierte Option prüfen, die nichts mit REST zu tun hat.

gRPC


gRPC (gRPC - Remote Procedure Call) ist ein von Google entwickeltes Open Source Remote Procedure Call-System. Dies ist ein bisschen wie REST in dem Sinne, dass es eine Möglichkeit bietet, Anforderungen vom Client an den Server zu senden. Aber das ist sehr unterschiedlich, hier sind die Ähnlichkeiten und Unterschiede:

  • GRPC ist wie REST sprachunabhängig. Es gibt Tools für alle gängigen Sprachen, einschließlich C #.
  • gRPC ist vertragsbasiert und verwendet .proto- Dateien, um den Vertrag zu definieren. Dies ähnelt in gewisser Weise Swagger swagger.json und der allgemeinen ReFit-Schnittstelle. Aus diesen Dateien kann ein Client einer beliebigen Programmiersprache generiert werden.
  • 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 .

Es gibt zwei Möglichkeiten, gRPC zu verwenden. Für .NET Core 3.0 gibt es eine vollständig verwaltete gRPC für .NET- Bibliothek . Sie können auch gRPC C # verwenden . Dies bedeutet nicht , dass gRPC für .NET ersetzt gRPC C # . Sehen wir uns ein Beispiel mit dem neueren gRPC für .NET an .

GRPC für .NET-Serverseite


Dies ist kein Leitfaden, sondern eine allgemeine Vorstellung davon, was Sie erwartet. So würde ein Beispiel-Controller in gRPC aussehen:


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

Sie müssen Folgendes zu Configure in der Datei Startup.cs hinzufügen :


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

Die API wird in der .proto- Datei beschrieben, die Teil des Projekts ist:

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

Diese .proto- Datei wird in die .csproj-Datei eingefügt:

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

GRPC für .NET-Clientseite


Der Client wird aus .proto- Dateien generiert . Der Code selbst ist sehr einfach:


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 klingt nach einem guten Geschäft. Unter der Haube geht es schneller und einfacher. Sollen wir also alle von REST zu gRPC wechseln? Die Antwort ist, dass es darauf ankommt . Hier einige Überlegungen:

Nach meiner Erfahrung ist die Arbeit mit gRPC und ASP.NET noch klein. Mit ausgereifter REST-Unterstützung sind Sie besser dran. Was die Vertragskommunikation betrifft, ist dies gut, außer dass Sie in REST ähnliche Alternativen haben, über die wir bereits gesprochen haben: Swagger und ReFit.

Der größte Vorteil ist die Leistung. In den meisten Fällen ist gRPC nach diesen Kriterien viel schneller. Besonders für große Nutzlasten, für die die Protobuf-Serialisierung wirklich wichtig ist. Dies bedeutet, dass dies ein großer Vorteil für einen Server mit hoher Auslastung ist.

Der Wechsel von REST zu gRPC in einer großen ASP.NET-Anwendung ist schwierig. Wenn Sie jedoch über eine auf Mikroservices basierende Architektur verfügen, wird dieser Übergang nach und nach viel einfacher abzuschließen.

Andere Kommunikationswege


Es gibt verschiedene andere Kommunikationsarten, die ich überhaupt nicht erwähnt habe, aber es lohnt sich zu wissen, dass sie existieren:

  • GraphQL ist eine von Facebook entwickelte API-Abfragesprache. Dadurch kann der Client genau die Daten anfordern, die er vom Server benötigt. Auf diese Weise können Sie nur einen Endpunkt auf dem Server erstellen, der äußerst flexibel ist und nur die Daten zurückgibt, die der Client benötigt. GraphQL ist in den letzten Jahren sehr beliebt geworden.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF ist eine alte Technologie, die hauptsächlich SOAP-basierte Kommunikation zwischen Prozessen verwendet. Dies ist ein riesiges Framework, auf das ich nicht eingehen werde. Ich kann nur sagen, dass es seine Popularität für REST- und JSON-Nutzdaten verloren hat.

Das ist alles. Ich hoffe der Artikel war interessant! Guter Code an alle!

All Articles