GraphQL ist eine Technologie die von Facebook entwickelt wurde die es dem Frontend erlaubt mit dem Backend zu sprechen, welche Daten es benötigt. Es ermöglicht den Entwicklern der API auch Daten aus verschiedenen anderen APIs zusammenzuführen. Es ist eine neue coole Technologie die nicht nur für Javascript gedacht ist. In diesem Artikel zeige ich dir, wie du sie in einen nächsten Projekt mit C# und .NET Core einsetzen kannst.

Wieso brauchen wir das?

GraphQL erlaubt euch so genannte content negotiation durchzuführen. Das bedeutet, dass du nur einen Endpunkt hast und es erlaubt dir auch genau nach den Feldern welche du haben möchtest abzufragen. Im Backend kannst die benötigten Daten dann aus verschiedenen Quellen zusammenbauen.

Aus welchen Bereichen setzt sich GraphQL zusammen?

GraphQL besteht aus einer Schema Definition und sogenannten resolver.

Das Schema kann in Graph Query Language (GQL) definiert werden. Man kann aber auch Klassen dekorieren, so dass diese entsprechend auf Anfragen antworten. Mehr dazu aber in diesem Artikel.

Ein resolver ist eine Funktion die weiß was mit einem eingehenden Query gemacht werden soll. Ein resolver ist auf spezielle Ressource gemapped.

Wie fragt man ab?

GraphQL sieht etwas gewöhnungsbedürftig aus, aber ist doch recht einfach, wenn man es erstmal eine Weile verwendet hat. Ein GraphQL Query mit Parametern könnte in etwa so aussehen:

{
  users(id:1) {
    name,
    id,
    last-login
  }
}

Das Resultat ist immer eine JSON Struktur die unserer Abfrage entspricht. Die Antwort auf die obige Abfrage könnte also in etwa folgendes sein:

{
  "data": {
    "users": [{
      "name" : "matthias",
      "id": 1
      "last-login": "2019-04-01"  
    }]
  }
}

Demo

Von der Theorie gehts jetzt in die Praxis. Jetzt zeige ich euch, wie ihr GraphQL mit .NET Core in C# umsetzen könnt. Wir werden folgendes machen:

  1. Eine Solution erzeugen
  2. Eine Konsolenanwendung erstellen
  3. GraphQL Paket aus NuGet installieren
  4. Die Anwendung implementieren

Starten wir damit die Solution mittels Terminal zu erzeugen. Geht in einen Ordner indem ihr euren Sourcecode verwaltet und gebt die folgenden Befehle im Terminal ein:

mkdir GraphQL
cd GraphQL

Jetzt erstellen wir mit dotnet Core unser Solution Template:

dotnet new sln

Anschließend fügen wir noch eine Konsolenanwendung ein:

dotnet new console -o App

Nun ab ins Verzeichnis der Konsolenapplikation und die GraphQL NuGet Bibliothek herunterladen und hinzufügen

cd App
dotnet add package GraphQL

Jetzt können wir folgenden Code in der Program.cs unserer Konsolenanwendung hinzufügen:

using System;
using GraphQL;
using GraphQL.Types;

namespace App
{
  class Program
  {
        static void Main(string[] args)
        {
            var schema = Schema.For(@"
                type Query {
                    greeting: String
                }
            ");

            var root = new {greeting = "Greetings, Friend!"};
            var json = schema.Execute(_ => 
            {
                _.Query = "{ greeting }";
                _.Root = root;
            });

            Console.WriteLine(json);
        }
  }
}

In diesem Code passieren 3 interessante Dinge:

  1. Wir deklarieren unser Schema
  2. Wir definieren unseren resolver
  3. Wir führen den Query aus

Der string den wir als Parameter in den Aufruf von Schema.For übergeben, enthält das weiter oben erwähnte GQL. Es definiert 2 Dinge:

  1. Query -> Das ist, was wir abfragen können
  2. Typen -> Welche Typen wir abfragen können
var schema = Schema.For(@"
                type Query {
                    greeting: String
                }
            ");

Bisher haben wir nur den Typ Query. Dieser sagt aus, dass wir nach dem Feld greeting abfragen können und einen string als Antwort bekommen werden.

Ein Schema ist gut und schön, aber wir brauchen noch einen resolver der die eingehenden Abfragen abhandelt. In unserem Fall sieht der resolver Code noch recht unspektakulär aus.:

var root = new {greeting = "Greetings, Friend!"};

Wie wir sehen, prüft der resolver ob die Abfrage nach greeting fragt und falls dies so ist, gibt er den string „Greetings, Friend!“ zurück.

Im folgenden Code führen wir die Abfrage { gretting } aus indem wir es der Eigenschaft _.Query in dem Lambda Ausdruck innerhalb von schema.Execute() zuweisen

            var json = schema.Execute(_ => 
            {
                _.Query = "{ greeting }";
                _.Root = root;
            });

Wir speichern den Rückgabewert in eine Variable Namens json und erhalten bei der Ausgabe über den Console.WriteLine(json) Befehl folgendes:

{
  "data": {
    "greeting": "Greetings, Friend!"
  }
}

Jetzt haben wir ordentlich etwas geschafft, aber wie können wir das ganze nun mit unseren eigenen Klassen bewerkstelligen?

Das ist ein anderer Ansatz. Nun werden wir Klassen erzeugen und diese entsprechend dekorieren. Die Idee dahinter ist, dass die Klassen Methoden haben, welche uns anschließend als resolver dienen. Um das zu erreichen müssen wir folgendes machen:

  1. Unser Schema überarbeiten, so dass wir eigene Typen einfügen können und diese im Query anbieten können.
  2. Klassen erzeugen, welche wir brauchen um unseren Typen abzubilden und die wir im Speicher halten können.

Schema aktualisieren

Bisher hatte unser Schema keine eigenen Typen. Das ändern wir jetzt indem wir einen neuen Typen hinzufügen:

var schema = Schema.For(@"
                type Cat {
                    name: String,
                    legs: Int
                }

                type Query {
                    greeting: String,
                    cats: [Cat]
                }
            ", _=> {
                _.Types.Include<Query>();
            });

Wir fügen Cat als Typ hinzu und definieren seine Eigenschaften innerhalb der geschwungenen Klammern ( {} ). Dann fügen wir die cats in unseren Query hinzu, so dass wir nach den Katzen abfragen können.

 _.Types.Include<Query>();

Hier geben wir an, dass es einen Klasse Query gibt, welche alle eingehenden Anfragen abhandelt. Diese Klasse existiert jedoch noch nicht, also fügen wir sie nun hinzu.

    public class Query
    {
        [GraphQLMetadata("cats")]
        public IEnumerable<Cat> GetCats()
        {
            return AnimalDatabase.GetCats();
        }

        [GraphQLMetadata("greeting")]
        public string GetGreeting()
        {
            return "Greetings, Friend!";
        }
    }

Die Klasse ist sehr einfach aufgebaut. Sie hat zwei Methoden welche durch die Attribute GraphQLMetadata auf die Eigenschaften in unserem Query verweisen. Wir machen auch einen Aufruf auf die AnimalDatabase um alle Katzen auszulesen, also müssen wir diese noch erzeugen.

    public static class AnimalDatabase
    {
        public static IEnumerable<Cat> GetCats()
        {
            return new List<Cat>() {
                new Cat(){ Name = "Mittens", Legs = 4},
                new Cat(){ Name = "Medo", Legs = 4},
                new Cat(){ Name = "Samantha", Legs = 3},
                new Cat(){ Name = "Gino", Legs = 4},
                new Cat(){ Name = "Baron", Legs = 4},
                new Cat(){ Name = "Charles", Legs = 4},
                new Cat(){ Name = "Finn", Legs = 4},
                new Cat(){ Name = "Lancelot", Legs = 3},
            };
        }
    }

Nun müssen wir nur noch unsere Katzenklasse anlegen.

    public class Cat
    {
        public string Name { get; set; }
        public int Legs { get; set; }
    }

Die Katzen abfragen

Wie fragen wir nun die Katzen in der Katzendatenbank ab? Wir müssen dafür eine Abfrage definieren. Diese könnte so aussehen:

{ cats { name, legs} }

Wir könnten genau diese Abfrage auch gegen einen SQL Server machen, dann würde die Abfrage in SQL so aussehen:

SELECT name, legs
FROM cats;

Wir müssen also die obere Abfrage nun in unsren Code schreiben, welcher die Abfrage ausführt.

var json = schema.Execute(_ => 
            {
                _.Query = "{ cats { name, legs } }";
            });

Die Ausgabe sieht nun wie folgt aus:

{
  "data": {
    "cats": [
      {
        "name": "Mittens",
        "legs": 4
      },
      {
        "name": "Medo",
        "legs": 4
      },
      {
        "name": "Samantha",
        "legs": 3
      },
      {
        "name": "Gino",
        "legs": 4
      },
      {
        "name": "Baron",
        "legs": 4
      },
      {
        "name": "Charles",
        "legs": 4
      },
      {
        "name": "Finn",
        "legs": 4
      },
      {
        "name": "Lancelot",
        "legs": 3
      }
    ]
  }
}

Das sieht schon mal super aus! Jetzt sind wir auch schon fast am Ende, aber eine Sache fehlt uns jedoch noch.

Abfragen mit Parametern

In den meisten Fällen möchten wir nicht einfach alle Ergebnisse aus der Datenbank haben, sondern nur bestimmte. Leider haben einige Katzen in unserer Datenbank nur 3 Beine und wir würden gerne auch nur nach diesen abfragen. Vielleicht um die nächsten Tierarztbesuche zu planen.

Um nun Parameter in unsere Abfragen hinzuzufügen, müssen wir folgendes tun:

  1. Unser Schema aktualisieren, damit es Parameter akzeptiert.
  2. Eine Methode in unserer Query Klasse schreiben, welche den Parameter verarbeiten kann.
  3. Überprüfen ob alles korrekt funktioniert.

Als erstes werden wir nun unser Schema aktualisieren.

type Query {
       greeting: String,
       cats: [Cat],
       catsfiltered(legs: Int): [Cat]
}

Wir haben nun unseren Query erweitert, dass wir einen Parameter über die Anzahl der Beine mitgeben können und erwarten uns als Antwort wiederum eine Liste.

Nun fügen wir noch eine Methode in unserem Query hinzu, wo wir den Parameter auf die Katzendatenbank anwenden.

[GraphQLMetadata("catsfiltered")]
public IEnumerable<Cat> GetCats(int legs)
{
    List<Cat> cats = AnimalDatabase.GetCats().ToList();
    return cats.Where(cat => cat.Legs == legs);
}

Nun müssen wir noch unseren Query aktualisieren.

_.Query = "{ catsfiltered(legs: 3) { name, legs } }";

Nun sollten wir alles erledigt haben. Bleibt nur noch zu überprüfen, ob alles korrekt funktioniert. Unsere Ausgabe zeigt uns folgendes:

{
  "data": {
    "catsfiltered": [
      {
        "name": "Samantha",
        "legs": 3
      },
      {
        "name": "Lancelot",
        "legs": 3
      }
    ]
  }
}

Das sieht gut aus und wir haben nur die 2 Datensätze erhalten, die unsere dreibeinigen Katzen zeigen.

Zusammenfassung

Abschließend kann man sagen, GraphQL ist keine Raketenwissenschaft. Wir haben uns angesehen, wie man Queries in GQL schreibt und wie man ein Schema aufbaut. Auch wie man seine eigene Klassen dekorieren kann um relativ schnell eine GraphQL API aufzubauen. Es gibt sicher einige Anwendungsfälle in denen GraphQL interessanter sein kann, als ein „klassischer“ REST Ansatz!

Falls du diesen Beitrag in VSCode nachprogrammierst, hilft dir vielleicht dieser Link bei deinen Einstellungen des launch.json Files.

.NET Core Debugging mit VSCode