4. REST vs. gRPC¶
4.1. Was ist eine REST API?¶
Eine REST API (Representational State Transfer Application Programming Interface) ist ein Architekturstil für verteilte Hypermediasysteme, der es ermöglicht, über das HTTP-Protokoll zu kommunizieren. REST APIs basieren auf einer Client-Server-Architektur, bei der der Client für die Benutzeroberfläche verantwortlich ist und der Server die Daten speichert und verarbeitet. REST zeichnet sich durch seine Zustandslosigkeit aus, was bedeutet, dass jede Anfrage vom Client an den Server alle Informationen enthält, die der Server benötigt, um die Anfrage zu verstehen und zu verarbeiten.
REST APIs nutzen die HTTP-Methoden GET, POST, PUT und DELETE, um CRUD-Operationen (Create, Read, Update, Delete) durchzuführen. Ressourcen werden durch URLs identifiziert und die Daten können in Formaten wie JSON oder XML übertragen werden. Ein weiterer wichtiger Aspekt von REST ist die Cachebarkeit, wodurch Antworten des Servers explizit als cachebar oder nicht-cachebar markiert werden können, um die Leistung zu verbessern.
Durch die Verwendung einer einheitlichen Schnittstelle können REST APIs in verschiedenen Sprachen und auf verschiedenen Plattformen implementiert werden. Typische HTTP-Statuscodes, die von REST APIs verwendet werden, umfassen 200 OK für erfolgreiche Anfragen, 201 Created für erfolgreich erstellte Ressourcen und 404 Not Found, wenn eine Ressource nicht gefunden wurde.
4.2. Was ist gRPC?¶
gRPC (Remote Procedure Calls) wird für die Interprozesskommunikation in verteilten Server-Client-Systemen verwendet. Standardmäßig wird dafür HTTP/2 verwendet. Die Daten werden in sogenannten Protocol Buffers strukturiert und verarbeitet.
Außerdem basiert es auf der Idee, einen Service zu definieren, deren Methoden von außerhalb mit ihren Parametern aufrufbar sind und einen Typ zurückgeben. Auf Seiten des Servers wird ein Interface implementiert, das einen gRPC Server laufen lässt und die Aufrufe des Clients behandelt. Auf Seiten des Clients gibt es einen sog. „Stub“, der die selben Methoden bereitstellt wie der Server.
4.2.1. Protocol Buffers¶
Protocol Buffer sind ein Mechanismus um sprachenunabhängig strukturierte Daten zu serialisieren. Es ist ähnlich zu JSON, generiert aber native Sprachanbindungen (language bindings). Um Protocol Buffer zu benutzen, muss man nur einmal definieren wie die Daten strukturiert sein sollen. Diese Daten können dann von allen von gRPC unterstützten Programmiersprachen gelesen werden (u.a. C++, C#, Java, Kotlin, Python, Ruby, Go).
Protocol Buffer sind eine Kombination aus der der Datendefinition (Inhalt der .proto Dateien), der vom Proto Compiler generierte Code, Sprachenspezifische runtime Bibliotheken,
dem Serialisationsformat für die Daten und der serialisierten Daten selbst.
Protobuf Daten sind als sog. messages strukturiert. Jede message ist eine logische Einheit, die Name-Value Paare enthält.
gRPC Services werden ebenfalls in .proto Dateien definiert. Die Methoden werden mit RPC Parametern definiert und geben Typen zurück, die als
Protobuf messages definiert sind.
4.3. Beispiel REST vs gRPC Client / Server¶
4.3.1. gRPC¶
Zuerst muss der Service und die zugehörigen Messages definiert werden. Für unser einfaches Beispiel sieht das so aus:
syntax = "proto3";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string reply_msg = 1;
}
Die Syntax-Zuweisung ist ein Signal für Protoc den Compiler. Wenn keine solche Anweisung angegeben wird,
wird per default der Syntax proto2 verwendet. Wenn die Definition abgeschlossen ist, kann man
den tatsächlichen „Anwendungscode“ generieren lassen mittels grpc_tools.protoc. Man kann hier Serialisationsformat
angeben welche Directories miteingebunden werden sollen, welches das Output-Directory ist und welche spezielle .proto
Datei compiliert werden soll.
python -m grpc_tools.protoc -Iprotos --python_out=. --pyi_out=. --grpc_python_out=. ./protos/hello_gRPC.proto
Nachdem der Code generiert wurde, muss man nun noch einen Server bzw. einen Client erstellen.
Der Server muss alle Methoden des vorher definierten Services implementieren. Das wird typischerweise mittels Klassenvererbung gemacht.
Die generierte Serviceklasse heißt *Servicer, im Fall unseres Beispiels GreeterServicer. Die Implementierung unsereres
Beispiels sieht so aus:
class Greeter(hello_gRPC_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return hello_gRPC_pb2.HelloReply(reply_msg=f'Hello, {request.name}!')
def SayHelloAgain(self, request, context):
return hello_gRPC_pb2.HelloReply(reply_msg=f'Hello again, {request.name}!')
Um tatsächlich einen gRPC-Server zu starten muss der Service noch hinzugefügt werden. Wir tun das
mittels der serve Methode. Der Server kann dann wie ein übliches Python Script gestartet werden.
def serve():
port = "8000"
server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
hello_gRPC_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port("[::]:" + port)
server.start()
print("Server started, listening on port " + port)
server.wait_for_termination()
Zuletzt muss natürlich auch noch ein Client erstellt werden, der den Service des Servers nutzt.
Dafür muss ein Stub erstellt werden. Mittels diesem Stub können dann die rpcs des Services
genutzt werden:
def run():
with grpc.insecure_channel("localhost:8000") as channel:
# Stub erstellen
stub = hello_gRPC_pb2_grpc.GreeterStub(channel)
# SayHello rpc nutzen
response = stub.SayHello(hello_gRPC_pb2.HelloRequest(name="gRPC"))
print("Gretter stub client received: " + response.reply_msg)
response = stub.SayHelloAgain(hello_gRPC_pb2.HelloRequest(name="gRPC"))
print("Gretter stub client received: " + response.reply_msg)
4.3.2. REST¶
1. Program.cs
Die Program.cs-Datei fungiert als zentraler Einstiegspunkt der Anwendung. An dieser Stelle erfolgt die Konfiguration verschiedener Dienste für die Containerverwaltung, beispielsweise die Unterstützung für Controller und Swagger, welche für die Dokumentation des APIs verwendet wird. Zudem werden die HTTPS-Weiterleitung und die Autorisierung festgelegt, um die Sicherheit der Anwendung zu gewährleisten. Durch die Zuordnung der Controller-Routen wird definiert, wie die HTTP-Anforderungen verarbeitet werden sollen. Schließlich wird die Anwendung gestartet, wodurch der Server bereitgestellt wird, um eingehende Anfragen zu empfangen und zu verarbeiten.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. PersonController.cs
Dies ist der Controller, der die Endpunkte für das Personen-API definiert.
Er vererbt von der ControllerBase Klasse und kann dementsprechend einige Methoden implementieren.
Auch besitzt er testweise eine kleine Liste, in der einige Objekte einer Personenklasse gespeichert sind.
In unserem Beispiel werden die HTTP-Requests `GET`, `POST` und `DELETE` implementiert.
[HttpGet("GetPersons")]
public ActionResult Get() {
return Ok(Context.ToArray());
}
In diesem Abschnitt wird eine Methode definiert, die `GetPersons` heißt und mithilfe
eines HTTP-Get-Requests von einem Client aufgerufen werden kann. Sie liefert einen StatusCode, hier `200 OK`, und die Liste in
JSON-Format zurück.
[HttpGet("GetPerson/{id:int}")]
public ActionResult Get(int id) {
return Ok(Context.FirstOrDefault(p => p!.Id == id));
}
Diese Methode liefert funktioniert wie die obere Methode, nur mit dem Unterschied, dass hier eine Person mit einer bestimmten ID zurückgeliefert wird.
[HttpPost("PersonAddUpdate")]
public ActionResult Update(Person? person) {
// Check if person already exists
if (Context.Any(p => p!.Id == person.Id)) {
var existingPerson = Context.FirstOrDefault(p => p!.Id == person.Id);
if (existingPerson == null)
return BadRequest();
existingPerson.Vorname = person.Vorname;
existingPerson.Nachname = person.Nachname;
existingPerson.Alter = person.Alter;
existingPerson.Immatrikulationsnummer = person.Immatrikulationsnummer;
existingPerson.Hobbys = person.Hobbys;
}
// Add new person
else {
if (Context.Count > 0)
person.Id = Context.Max(p => p!.Id) + 1;
else
person.Id = 0;
Context.Add(person);
}
return Ok(person);
}
Hier kann der Client einen HTTP-Post-Request aufrufen und diesem ein Personenobjekt im JSON-Format mitgeben. Es wird zunächst nach Edge-Cases geprüft und anschließend werden die Werte eines bestehenden Personenobjektes mit den neuen Werten überschrieben.
[HttpDelete("PersonDelete/{id:int}")]
public ActionResult Delete(int id) {
// Check if person exists
var person = Context.FirstOrDefault(p => p!.Id == id);
if (person == null)
return NotFound();
// Remove person
Context.Remove(person);
return Ok();
}
Mithilfe eines HTTP-Delete-Requests und einer ID kann man eine bestimmte Person aus der Liste rauslöschen.
3. Person.cs Die Person.cs-Datei definiert das Modell einer Person für das RESTful API. Sie enthält Attribute wie ID, Vorname, Nachname, Alter, Immatrikulationsnummer und Hobbys. Diese Attribute repräsentieren die grundlegenden Informationen, die für jede Person innerhalb des Systems benötigt werden, und ermöglichen deren Verwaltung über das API.
public class Person {
public int Id { get; set; }
public string Vorname { get; set; }
public string Nachname { get; set; }
public int Alter { get; set; }
public string Immatrikulationsnummer { get; set; }
public string Hobbys { get; set; }
}
4.4. Vergleich REST und gRPC¶
gRPC |
REST API |
|
|---|---|---|
Aufgabe |
ermöglicht den Datenaustausch zwischen zwei Systemen (Client und Server) anhand von gemeinsamen Regelwerk |
|
Kommunikations- protokoll |
HTTP/2 |
HTTP |
Programmiersprachen |
Viele Programmiersprachen möglich z.B. Python, Java |
|
Kommunikations- modell |
Client-Response-Modell: Client sendet eine / mehrere Anfragen und erhält eine / mehrere Antworten vom Server |
Anfrage-Antwort-Modell: Client sendet eine Anfrage und erhält eine Antwort vom Server |
Server-Operationen |
durch die API definiert |
begrenzt durch HTTP-Methoden: GET, POST, PUT, DELETE, … |
Datenformat |
Protocol-Buffers-Format |
JSON (Standard), XML, HTML |
Client-Server- Kopplung |
Beide müssen Zugriff auf dieselben Proto-Dateien haben |
Beide wissen nichts von der Implementierungung des anderen |
4.5. Literatur¶
RESTAPI: https://ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf, https://restfulapi.net/ gRPC Docs: https://grpc.io/docs/what-is-grpc/introduction/ (08.05.2024)
Protocol Buffers Docs: https://protobuf.dev/overview/ (08.05.2024)
C# WebAPI (REST): https://learn.microsoft.com/de-de/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api (14.05.2024)
gRPC vs REST: https://aws.amazon.com/de/compare/the-difference-between-grpc-and-rest (15.05.2024)