Compréhension du système de types GraphQL

L’auteur a sélectionné le Fonds Libre et Open Source pour recevoir un don dans le cadre du programme Write for DOnations.

Introduction

GraphQL est une solution moderne pour faciliter la communication entre un front-end et une source de données. Tous les détails et les capacités d’une implémentation GraphQL sont exposés dans le Schéma GraphQL. Pour écrire un schéma GraphQL fonctionnel, vous devez comprendre le Système de Types GraphQL.

Dans cet article, vous apprendrez les types GraphQL : les cinq types scalaires intégrés, les Énumérations, les types d’enveloppement Liste et Non-Null, les Types d’Objet, et les types abstraits Interface et Union qui travaillent avec eux. Vous passerez en revue des exemples pour chaque type et apprendrez comment les utiliser pour construire un schéma GraphQL complet.

Prérequis

Pour tirer le meilleur parti de ce tutoriel, vous devriez avoir:

Types Scalaires

Toutes les données dans un schéma GraphQL se résolvent finalement en différents types scalaires, qui représentent des valeurs primitives. Les réponses GraphQL peuvent être représentées sous forme d’arbre, et les types scalaires sont les feuilles à l’extrémité de l’arbre. Il peut y avoir plusieurs niveaux dans une réponse imbriquée, mais le dernier niveau se résoudra toujours à un type scalaire (ou Enum). GraphQL est livré avec cinq types scalaires intégrés : Int, Float, String, Boolean, et ID.

Int

Int est une valeur numérique signée sur 32 bits sans partie fractionnaire. C’est un entier signé (positif ou négatif) qui n’inclut pas de décimales. La valeur maximale d’un entier signé sur 32 bits est 2 147 483 647. Il s’agit de l’un des deux scalaires intégrés utilisés pour les données numériques.

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

Un ID est un identifiant unique. Cette valeur est toujours sérialisée sous forme de chaîne, même si l’ID est numérique. Un type ID peut être couramment représenté par un Identifiant Universel Unique (UUID).

Scalars Personnalisés

En plus de ces scalaires intégrés, le mot-clé scalar peut être utilisé pour définir un scalaire personnalisé. Vous pouvez utiliser des scalaires personnalisés pour créer des types qui ont une validation supplémentaire au niveau du serveur, tels que Date, Time ou Url. Voici un exemple définissant un nouveau type Date:

scalar Date

Le serveur saura comment gérer les interactions avec ce nouveau type en utilisant le GraphQLScalarType.

Type Énuméré

Le type Enum, également connu sous le nom de type Énumérateur, décrit un ensemble de valeurs possibles.

En utilisant le thème de l’API du jeu fantastique provenant d’autres tutoriels de la série Comment Gérer les Données avec GraphQL, vous pourriez créer une enum pour les Job et Species des personnages du jeu avec toutes les valeurs que le système acceptera pour eux. Un Enum est défini avec le mot-clé enum, comme ceci :

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

De cette manière, il est garanti que le Job d’un personnage est FIGHTER ou WIZARD et ne peut jamais être accidentellement "purple" ou une autre chaîne aléatoire, ce qui serait possible si vous utilisiez un type String au lieu de créer une énumération personnalisée. Les énumérations sont écrites en majuscules par convention.

Les énumérations peuvent également être utilisées comme valeurs acceptées dans les arguments. Par exemple, vous pourriez créer une énumération Hand pour indiquer si une arme est à une main (comme une épée courte) ou à deux mains (comme une hache lourde), et l’utiliser pour déterminer si une ou deux peuvent être équipées :

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]
}

L’énumération Hand a été déclarée avec les valeurs SINGLE et DOUBLE, et l’argument sur le champ weapons a une valeur par défaut de SINGLE, ce qui signifie que si aucun argument n’est passé, il reviendra à SINGLE.

Type Non-Null

Vous remarquerez peut-être que null ou undefined, un type commun que de nombreux langages considèrent comme primitif, est absent de la liste des scalaires intégrés. Null existe dans GraphQL et représente le manque d’une valeur.

Tous les types en GraphQL sont nullable par défaut et donc null est une réponse valide pour n’importe quel type. Pour rendre une valeur obligatoire, elle doit être convertie en un type GraphQL Non-Null avec un point d’exclamation en suffixe. Non-Null est défini comme un modificateur de type, qui sont des types utilisés pour modifier le type auquel il se réfère. Par exemple, String est une chaîne facultative (ou nullable), et String! est une chaîne requise (ou Non-Null).

Type de Liste

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.

Par exemple, un type défini comme [Int] sera une collection de types Int, et [String] sera une collection de types String. Non-Null et List peuvent être utilisés ensemble pour rendre un type à la fois requis et défini comme une liste, comme [String]!.

Type d’Objet

Si les types scalaires GraphQL décrivent les « feuilles » à la fin de la réponse GraphQL hiérarchique, alors les types Objet décrivent les « branches » intermédiaires, et presque tout dans un schéma GraphQL est un type d’objet.

Les objets se composent d’une liste de champs nommés (clés) et du type de valeur que chaque champ résoudra. Les objets sont définis avec le mot-clé type. Au moins un ou plusieurs champs doivent être définis, et les champs ne peuvent pas commencer par deux traits de soulignement (__) pour éviter les conflits avec le système d’inspection GraphQL.

Dans l’exemple de l’API du jeu de fantaisie GraphQL, vous pourriez créer un objet Fighter pour représenter un type de personnage dans un jeu :

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

Dans cet exemple, le type d’objet Fighter a été déclaré, et il a quatre champs nommés :

  • id produit un type ID Non-Null.
  • name produit un type String Non-Null.
  • level produit un type Int.
  • active produit un type Boolean Non-Null.

Au-dessus de la déclaration, vous pouvez également ajouter un commentaire en utilisant des guillemets doubles, comme dans cet exemple : "Un héros avec une capacité de combat directe et de la force.". Cela apparaîtra comme la description du type.

Dans cet exemple, chaque champ résout à un type scalaire, mais les champs d’objet peuvent également résoudre à d’autres types d’objets. Par exemple, vous pourriez créer un type Weapon, et le schéma GraphQL peut être configuré pour que le champ weapon sur le Fighter résolve à un objet Weapon :

"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
}

Les objets peuvent également être imbriqués dans les champs d’autres objets.

Types d’Opérations Racines

Il existe trois objets spéciaux servant de points d’entrée dans un schéma GraphQL : Query, Mutation et Subscription. Ils sont connus sous le nom de types d’opérations racines et suivent toutes les mêmes règles que n’importe quel autre type d’objet.

Le mot-clé schema représente le point d’entrée dans un schéma GraphQL. Vos types Query, Mutation et Subscription seront sur l’objet racine schema :

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

Le type Query est requis dans tout schéma GraphQL et représente une requête de lecture, similaire à une requête GET dans une API REST. Voici un exemple d’objet Query racine qui renvoie une liste de types Fighter :

type Query {
  fighters: [Fighter]
}

Les Mutations représentent une requête d’écriture, équivalente à un POST, PUT ou DELETE dans une API REST. Dans l’exemple suivant, la Mutation a un champ addFighter avec un argument nommé (input) :

type Mutation {
  addFighter(input: FighterInput): Fighter
}

Enfin, une Subscription correspond à un flux d’événements, utilisé en conjonction avec un Websocket dans une application web. Dans l’API Fantasy GraphQL, elle pourrait être utilisée pour des rencontres de bataille aléatoires, comme ceci :

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

Notez que le point d’entrée schema est souvent abstrait dans certaines implémentations GraphQL.

Arguments de Champ

Les champs d’un objet GraphQL sont essentiellement des fonctions qui renvoient une valeur, et ils peuvent accepter des arguments comme n’importe quelle fonction. Les arguments de champ sont définis par le nom de l’argument suivi du type. Les arguments peuvent être de n’importe quel type non-objet. Dans cet exemple, l’objet Fighter peut être filtré par le champ id (qui renvoie à un type ID Non-Null) :

type Query {
  fighter(id: ID!): Fighter
}

Cet exemple particulier est utile pour récupérer un seul élément depuis le magasin de données, mais les arguments peuvent également être utilisés pour le filtrage, la pagination et d’autres requêtes plus spécifiques.

Type d’Interface

Tout comme le type Objet, le type d’Interface abstrait se compose d’une liste de champs nommés et de leurs types de valeur associés. Les interfaces ressemblent à des objets et suivent toutes les mêmes règles, mais elles sont utilisées pour définir un sous-ensemble de l’implémentation d’un objet.

Jusqu’à présent dans votre schéma, vous avez un objet Fighter, mais vous voudrez peut-être également créer un Wizard, un Healer, et d’autres objets qui partageront de nombreux champs mais auront quelques différences. Dans ce cas, vous pouvez utiliser une interface pour définir les champs qu’ils ont tous en commun et créer des objets qui sont des implémentations de l’interface.

Dans l’exemple suivant, vous pourriez créer une interface BaseCharacter en utilisant le mot-clé interface avec tous les champs que chaque type de personnage possédera :

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

Chaque type de personnage aura les champs id, name, level, species, et job.

Maintenant, imaginez que vous avez un type Fighter et un type Wizard qui ont ces champs partagés, mais que les Fighters utilisent une Weapon et que les Wizards utilisent des Spells. Vous pouvez utiliser le mot-clé implements pour délimiter chacun comme une implémentation de BaseCharacter, ce qui signifie qu’ils doivent avoir tous les champs de l’interface créée :

"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 et Wizard sont tous deux des implémentations valides de l’interface BaseCharacter car ils ont le sous-ensemble requis de champs.

Union Type

Un autre type abstrait qui peut être utilisé avec des objets est le type Union. En utilisant le mot-clé union, vous pouvez définir un type avec une liste d’objets qui sont tous valides en tant que réponses.

En utilisant les interfaces créées dans la section précédente, vous pouvez créer une Union Character qui définit un personnage comme étant un Wizard OU un Fighter:

union Character = Wizard | Fighter

Le signe égal (=) définit la définition, et le symbole de la barre verticale (|) fonctionne comme l’opérateur OU. Notez qu’une Union doit être constituée d’objets ou d’interfaces. Les types scalaires ne sont pas valides sur une Union.

Maintenant, si vous interrogez une liste de personnages, vous pouvez utiliser l’Union Character et retourner tous les types Wizard et Fighter.

Conclusion

Dans ce tutoriel, vous avez appris sur plusieurs des types qui définissent le système de types GraphQL. Les types les plus fondamentaux sont les types scalaires, qui sont les valeurs agissant comme les feuilles sur l’arbre du schéma, et comprennent Int, Float, String, Boolean, ID, et tout type scalaire personnalisé qu’une implémentation GraphQL décide de créer. Les énumérations sont des listes de valeurs constantes valides qui peuvent être utilisées lorsque vous avez besoin de plus de contrôle sur une réponse que simplement la déclarer comme une String, et sont également des feuilles sur l’arbre du schéma. Les types Liste et Non-Null sont connus sous le nom de modificateurs de type, ou types de conteneur, et ils peuvent définir d’autres types comme des collections ou requis, respectivement. Les Objets sont les branches de l’arbre du schéma, et presque tout dans un schéma GraphQL est un type d’Objet, y compris les points d’entrée query, mutation, et subscription. Les types Interface et Union sont des types abstraits qui peuvent être utiles pour définir des Objets.

Pour continuer à apprendre, vous pouvez pratiquer la création et la modification d’un schéma GraphQL en lisant le tutoriel Comment Configurer un Serveur API GraphQL en Node.js pour avoir un environnement de serveur GraphQL fonctionnel.

Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system