Der Autor hat das Free and Open Source Fund ausgewählt, um im Rahmen des Write for Donations-Programms eine Spende zu erhalten.
Einführung
GraphQL ist eine moderne Lösung zur Erleichterung der Kommunikation zwischen einer Frontend-Anwendung und einer Datenquelle. Alle Details und Fähigkeiten einer GraphQL-Implementierung sind im GraphQL-Schema festgelegt. Um ein funktionierendes GraphQL-Schema zu erstellen, ist ein Verständnis des GraphQL-Typsystems erforderlich.
In diesem Artikel erfahren Sie mehr über GraphQL-Typen: die fünf integrierten skalaren Typen, Enums, die Typen Liste und Non-Null, Objekttypen sowie die abstrakten Interface– und Union-Typen, die mit ihnen zusammenarbeiten. Sie werden Beispiele für jeden Typ durchgehen und lernen, wie Sie sie verwenden können, um ein vollständiges GraphQL-Schema zu erstellen.
Voraussetzungen
Um das Beste aus diesem Tutorial herauszuholen, sollten Sie folgendes haben:
- Ein Verständnis der grundlegenden Konzepte von GraphQL, die in Eine Einführung in GraphQL erläutert werden.
- A GraphQL environment, an example of which can be found in How to Set Up a GraphQL API Server in Node.js.
Skalare Typen
Alle Daten in einem GraphQL-Schema lösen sich letztendlich in verschiedene skalare Typen auf, die primitive Werte repräsentieren. GraphQL-Antworten können als Baum dargestellt werden, und die skalaren Typen sind die Blätter am Ende des Baumes. Es können viele Ebenen in einer verschachtelten Antwort vorhanden sein, aber die letzte Ebene löst sich immer in einen skalaren (oder Enum) Typ auf. GraphQL wird mit fünf integrierten skalaren Typen geliefert: Int
, Float
, String
, Boolean
und ID
.
Int
Int
ist ein vorzeichenbehafteter 32-Bit-Ganzzahlwert ohne Dezimalstellen. Es handelt sich um eine vorzeichenbehaftete (positive oder negative) Ganzzahl, die keine Dezimalstellen enthält. Der maximale Wert einer vorzeichenbehafteten 32-Bit-Ganzzahl ist 2.147.483.647
. Dies ist einer der beiden integrierten Skalare, die für numerische Daten verwendet werden.
Float
A Float
is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2
. This is the other built-in scalar used for numerical data.
String
A String
is a UTF-8 character sequence. The String
type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.
Boolean
A Boolean
is a true
or false
value.
ID
Ein ID
ist ein eindeutiger Bezeichner. Dieser Wert wird immer als Zeichenkette serialisiert, auch wenn die ID
numerisch ist. Ein ID
-Typ kann häufig mit einer Universally Unique Identifier (UUID) dargestellt werden.
Benutzerdefinierte Skalare
Neben diesen integrierten Skalaren kann das Schlüsselwort scalar
verwendet werden, um einen benutzerdefinierten Skalar zu definieren. Benutzerdefinierte Skalare ermöglichen die Erstellung von Typen mit zusätzlicher Servervalidierung, wie zum Beispiel Date
, Time
oder Url
. Hier ist ein Beispiel für die Definition eines neuen Typs Date
:
scalar Date
Der Server wird wissen, wie er mit diesem neuen Typ durch die Verwendung von GraphQLScalarType
interagieren soll.
Enum-Typ
Der Enum-Typ, auch als Enumerator-Typ bekannt, beschreibt eine Reihe möglicher Werte.
Unter Verwendung des Fantasy Game API-Themas aus anderen Tutorials der How To Manage Data with GraphQL-Serie könnten Sie ein enum
für die Job
– und Species
-Eigenschaften der Spielfiguren mit allen akzeptierten Werten erstellen. Ein Enum wird mit dem Schlüsselwort enum
definiert, wie folgt:
"The job class of the character."
enum Job {
FIGHTER
WIZARD
}
"The species or ancestry of the character."
enum Species {
HUMAN
ELF
DWARF
}
Auf diese Weise ist garantiert, dass der Job
eines Charakters entweder FIGHTER
oder WIZARD
ist und niemals versehentlich "purple"
oder eine andere zufällige Zeichenkette sein kann, was möglich wäre, wenn Sie einen String
-Typ verwenden würden, anstatt ein benutzerdefiniertes Enum zu erstellen. Enums werden konventionell in Großbuchstaben geschrieben.
Enums können auch als akzeptierte Werte in Argumenten verwendet werden. Zum Beispiel könnten Sie ein Hand
enum
erstellen, um anzugeben, ob eine Waffe einhändig (wie ein Kurzschwert) oder zweihändig (wie eine schwere Axt) ist, und das verwenden, um festzulegen, ob eine oder zwei ausgerüstet werden können:
enum Hand {
SINGLE
DOUBLE
}
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
hand: Hand
}
type Query {
weapons(hand: Hand = SINGLE): [Weapon]
}
Das Hand
enum
wurde mit den Werten SINGLE
und DOUBLE
deklariert, und das Argument im weapons
-Feld hat einen Standardwert von SINGLE
, was bedeutet, dass es auf SINGLE
zurückfällt, wenn kein Argument übergeben wird.
Non-Null Type
Sie werden vielleicht bemerken, dass null
oder undefined
, ein gemeinsamer Typ, den viele Sprachen als primitiv betrachten, in der Liste der integrierten Skalare fehlt. Null existiert in GraphQL und repräsentiert das Fehlen eines Wertes.
Alle Typen in GraphQL sind standardmäßig nullable, daher ist null
eine gültige Antwort für jeden Typ. Um einen Wert erforderlich zu machen, muss er in einen GraphQL Non-Null-Typ mit einem abschließenden Ausrufezeichen umgewandelt werden. Non-Null ist als Typmodifikator definiert, der Typen modifiziert, auf die er sich bezieht. Als Beispiel ist String
ein optionaler (oder nullable) String, und String!
ist ein erforderlicher (oder Non-Null) String.
Listentyp
A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]
) becomes a List type, which is a collection that defines the type of each item in a list.
Als Beispiel wird ein als [Int]
definierter Typ eine Sammlung von Int
-Typen sein, und [String]
wird eine Sammlung von String
-Typen sein. Non-Null und List können zusammen verwendet werden, um einen Typ sowohl erforderlich als auch als Liste definiert zu machen, wie zum Beispiel [String]!
.
Objekttyp
Wenn GraphQL-Skalartypen die „Blätter“ am Ende der hierarchischen GraphQL-Antwort beschreiben, dann beschreiben Objekt-Typen die vermittelnden „Äste“, und fast alles in einem GraphQL-Schema ist ein Typ von Objekt.
Objekte bestehen aus einer Liste benannter Felder (Schlüssel) und dem Werttyp, den jedes Feld auflösen wird. Objekte werden mit dem Schlüsselwort type
definiert. Mindestens ein oder mehrere Felder müssen definiert sein, und Felder dürfen nicht mit zwei Unterstrichen (__
) beginnen, um Konflikte mit dem GraphQL-Introspektionsystem zu vermeiden.
In dem Beispiel der GraphQL-Fantasy-Game-API könnten Sie ein Kämpfer
-Objekt erstellen, um einen Charaktertyp in einem Spiel darzustellen:
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
}
In diesem Beispiel wurde der Objekttyp Kämpfer
deklariert, und er hat vier benannte Felder:
id
ergibt einen Non-Null-TypID
.Name
ergibt einen Non-Null-TypString
.Level
ergibt einenInt
-Typ.Aktiv
ergibt einen Non-Null-TypBoolean
.
Über der Deklaration können Sie auch einen Kommentar mit doppelten Anführungszeichen hinzufügen, wie in diesem Beispiel: "Ein Held mit direkter Kampffähigkeit und Stärke."
. Dies erscheint als Beschreibung für den Typ.
In diesem Beispiel löst jedes Feld zu einem skalaren Typ auf, aber Objektfelder können auch zu anderen Objekttypen aufgelöst werden. Zum Beispiel könnten Sie einen Waffe
-Typ erstellen, und das GraphQL-Schema kann so eingerichtet werden, dass das waffe
-Feld auf dem Kämpfer
zu einem Waffe
-Objekt aufgelöst wird:
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
}
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
weapon: Weapon
}
Objekte können auch in die Felder anderer Objekte verschachtelt werden.
Wurzelbetriebstypen
Es gibt drei spezielle Objekte, die als Einstiegspunkte in ein GraphQL-Schema dienen: Abfrage, Mutation und Abonnement. Diese werden als Wurzelbetriebstypen bezeichnet und folgen allen gleichen Regeln wie jeder andere Objekttyp.
Das Schlüsselwort schema
stellt den Einstiegspunkt in ein GraphQL-Schema dar. Ihre Wurzelabfrage-, Mutations- und Abonnementtypen werden im Wurzelobjekt schema
sein:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
Der Abfrage-Typ ist in jedem GraphQL-Schema erforderlich und repräsentiert eine Leseanforderung, ähnlich wie ein REST-API GET
. Folgendes ist ein Beispiel für ein Wurzel-Abfrage
-Objekt, das eine Liste von Kämpfer
-Typen zurückgibt:
type Query {
fighters: [Fighter]
}
Mutationen repräsentieren eine Schreibanforderung, die analog zu einem POST
, PUT
oder DELETE
in einer REST-API wäre. Im folgenden Beispiel hat die Mutation
ein addFighter
-Feld mit einem benannten Argument (Eingabe
):
type Mutation {
addFighter(input: FighterInput): Fighter
}
Zuletzt entspricht ein Abonnement einem Ereignisstrom, der in Verbindung mit einem Websocket in einer Webanwendung verwendet würde. In der GraphQL Fantasy API könnte es beispielsweise für zufällige Kampfbegegnungen verwendet werden, wie folgt:
type Subscription {
randomBattle(enemy: Enemy): BattleResult
}
Beachten Sie, dass der Einstiegspunkt schema
in einigen GraphQL-Implementierungen oft abstrahiert wird.
Feldargumente
Die Felder eines GraphQL-Objekts sind im Wesentlichen Funktionen, die einen Wert zurückgeben, und sie können wie jede Funktion Argumente akzeptieren. Feldargumente werden durch den Namen des Arguments gefolgt vom Typ definiert. Argumente können jeden nicht-Objekttyp haben. In diesem Beispiel kann das Fighter
-Objekt nach dem id
-Feld gefiltert werden (das zu einem Nicht-Null-Typ ID
aufgelöst wird):
type Query {
fighter(id: ID!): Fighter
}
Dieses spezielle Beispiel ist nützlich, um ein einzelnes Element aus dem Datenspeicher abzurufen, aber Argumente können auch für Filterung, Paginierung und andere spezifischere Abfragen verwendet werden.
Interface-Typ
Wie der Objekttyp besteht der abstrakte Interface-Typ aus einer Liste benannter Felder und ihrer zugehörigen Werttypen. Schnittstellen sehen aus wie und folgen allen gleichen Regeln wie Objekte, werden jedoch verwendet, um einen Teil der Implementierung eines Objekts zu definieren.
Bisher haben Sie in Ihrem Schema ein Fighter
-Objekt, aber Sie möchten vielleicht auch ein Wizard
, ein Healer
und andere Objekte erstellen, die viele der gleichen Felder gemeinsam haben, aber einige Unterschiede aufweisen. In diesem Fall können Sie ein Interface verwenden, um die Felder zu definieren, die sie alle gemeinsam haben, und Objekte zu erstellen, die Implementierungen des Interfaces sind.
In folgendem Beispiel könnten Sie ein BaseCharacter
-Interface mit dem interface
-Schlüsselwort erstellen, das alle Felder definiert, die jeder Charaktertyp besitzen wird:
"A hero on a quest."
interface BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job
}
Jeder Charaktertyp wird die Felder id
, name
, level
, species
und job
haben.
Stellen Sie sich nun vor, Sie haben einen Fighter
-Typ und einen Wizard
-Typ, die diese gemeinsamen Felder haben, aber Fighters
verwenden ein Weapon
und Wizards
verwenden Spells
. Sie können das implements
-Schlüsselwort verwenden, um jeden als eine BaseCharacter
-Implementierung abzugrenzen, was bedeutet, dass sie alle Felder aus dem erstellten Interface haben müssen:
"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
weapon: Weapon
}
"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
spells: [Spell]
}
Fighter
und Wizard
sind beide gültige Implementierungen des BaseCharacter
-Interfaces, weil sie die erforderliche Teilmenge der Felder haben.
Union Type
Ein weiterer abstrakter Typ, der mit Objekten verwendet werden kann, ist der Union-Typ. Mit dem Schlüsselwort „union“ können Sie einen Typ mit einer Liste von Objekten definieren, die alle als Rückgabewerte gültig sind.
Unter Verwendung der in dem vorherigen Abschnitt erstellten Schnittstellen können Sie eine Character-Union erstellen, die einen Charakter als Zauberer ODER Kämpfer definiert:
union Character = Wizard | Fighter
Das Gleichheitszeichen (=) legt die Definition fest und das Pipe-Zeichen (|) dient als ODER-Anweisung. Beachten Sie, dass eine Union aus Objekten oder Schnittstellen bestehen muss. Skalare Typen sind in einer Union nicht gültig.
Wenn Sie nun nach einer Liste von Charakteren abfragen, kann die Character-Union verwendet werden und alle Zauberer- und Kämpfertypen zurückgeben.
Zusammenfassung
In diesem Tutorial haben Sie viele der Typen kennengelernt, die das GraphQL-Typsystem definieren. Die grundlegendsten Typen sind die Skalartypen, die die Werte darstellen, die als Blätter im Schema-Baum fungieren und aus Int, Float, String, Boolean, ID und jedem benutzerdefinierten Skalartyp bestehen, den eine GraphQL-Implementierung erstellt. Enums sind Listen gültiger konstanter Werte, die verwendet werden können, wenn Sie mehr Kontrolle über eine Antwort benötigen als nur die Deklaration als String, und sie sind ebenfalls Blätter im Schema-Baum. Listen- und Non-Null-Typen werden als Typmodifikatoren oder Wrapper-Typen bezeichnet und können andere Typen als Sammlungen oder erforderlich definieren. Objekte sind die Zweige des Schema-Baums, und fast alles in einem GraphQL-Schema ist ein Objekttyp, einschließlich der query-, mutation- und subscription-Einstiegspunkte. Interface- und Union-Typen sind abstrakte Typen, die bei der Definition von Objekten hilfreich sein können.
Für weiteres Lernen können Sie üben, ein GraphQL-Schema zu erstellen und zu ändern, indem Sie das Tutorial „Wie man einen GraphQL-API-Server in Node.js einrichtet“ lesen, um eine funktionierende GraphQL-Serverumgebung zu haben.
Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system