Retour au cours

backend / graphql

Types complexes : interfaces, unions, enums

Leçon 71 exercice

Explication

Ce que vous allez apprendre

  • Déclarer un enum et comprendre pourquoi il est plus sûr qu'une simple chaîne de caractères
  • Définir une interface pour garantir un socle de champs communs à plusieurs types
  • Définir une union pour représenter des types complètement hétérogènes
  • Utiliser des fragments inline (... on Type) pour accéder aux champs spécifiques
  • Savoir quand utiliser __typename et pourquoi il est indispensable avec une union

Dans quel contexte ?

Un centre de notifications d'une application doit afficher, dans une même liste, des notifications de commande expédiée et des notifications de promotion, chacune avec des champs différents. Une barre de recherche globale, elle, doit retourner des produits, des catégories et des utilisateurs dans une seule liste de résultats — des types qui n'ont, cette fois, aucun champ en commun. Ce sont deux situations distinctes, résolues par deux outils différents du langage : l'interface et l'union.

D'abord, le plus simple des trois : l'enum

Un enum comme StatutCommande liste un ensemble fermé de valeurs (EN_ATTENTE, EXPEDIEE, LIVREE, ANNULEE). Contrairement à une simple chaîne de caractères, le serveur rejette automatiquement toute valeur qui ne fait pas partie de cette liste, aussi bien en entrée (argument) qu'en sortie (champ) — une garantie que String seul ne peut jamais offrir.

Ensuite, un cas plus riche : plusieurs types qui partagent un socle commun

Une interface comme Notification déclare des champs (id, creeLe, lu) que tout type l'implémentant doit obligatoirement fournir. NotificationCommande et NotificationPromo implémentent tous deux Notification, mais chacun ajoute ses propres champs spécifiques (commande pour l'un, pourcentageReduction pour l'autre). Un champ comme notifications: [Notification!]! peut alors renvoyer une liste hétérogène tout en garantissant que id, creeLe et lu sont toujours accessibles sur chaque élément, sans savoir à l'avance de quel type concret il s'agit.

Il reste un problème : comment accéder aux champs SPÉCIFIQUES d'un type dans une interface ?

C'est le rôle du fragment inline, écrit ... on NotificationCommande { commande { id statut } } directement dans la sélection. Le client peut alors mélanger les champs communs de l'interface et les champs spécifiques à chaque type concret, dans une seule requête.

ConstructionChamps garantis en commun ?Cas d'usage typique
enum— (valeur unique parmi un ensemble fermé)Statuts, catégories fermées de valeurs
interfaceOui, un socle partagé obligatoireNotifications, éléments d'un fil d'actualité
unionNon, aucun champ commun garantiRésultats de recherche globale hétérogènes

Maintenant, le cas où même le socle commun n'existe pas

Une union comme ResultatRecherche = Produit | Categorie | Utilisateur regroupe des types qui n'ont, cette fois, aucun champ garanti en commun — contrairement à l'interface. La conséquence directe : une union exige TOUJOURS des fragments inline pour accéder à quoi que ce soit, même le champ le plus basique.

Piège fréquent

Oublier le méta-champ __typename dans une sélection sur une union est une erreur fréquente : sans lui, le client reçoit une réponse où chaque élément de la liste a une forme différente, sans aucun moyen fiable de savoir, côté JavaScript ou Python, à quel type concret il correspond pour choisir le bon composant d'affichage.

Prérequis

Cette leçon suppose que tu es à l'aise avec les fragments nommés (leçon sur les queries) : le fragment inline en est une variante, appliquée directement dans la sélection plutôt que déclarée séparément.

Une fois ces trois outils de modélisation maîtrisés, la prochaine leçon aborde un problème très concret dès qu'on manipule des listes : comment paginer proprement de grandes collections avec la pagination par curseur (convention Relay), bien plus robuste qu'une simple pagination par offset.

Commandes & code

Types complexes : interfaces, unions, enums

graphql
# Enum : ensemble fermé de valeurs possibles, validé par le serveur
enum StatutCommande {
  EN_ATTENTE
  EXPEDIEE
  LIVREE
  ANNULEE
}

type Commande {
  id: ID!
  statut: StatutCommande!
}

# Interface : contrat commun à plusieurs types, garantit un socle de champs partagés
interface Notification {
  id: ID!
  creeLe: String!
  lu: Boolean!
}

type NotificationCommande implements Notification {
  id: ID!
  creeLe: String!
  lu: Boolean!
  commande: Commande!
}

type NotificationPromo implements Notification {
  id: ID!
  creeLe: String!
  lu: Boolean!
  pourcentageReduction: Int!
}

type Query {
  # Retourne une liste hétérogène mais garantit id/creeLe/lu sur chaque élément
  notifications: [Notification!]!
}
graphql
# Interrogation d'une interface avec fragments inline pour les champs spécifiques
query {
  notifications {
    id
    lu
    ... on NotificationCommande {
      commande { id statut }
    }
    ... on NotificationPromo {
      pourcentageReduction
    }
  }
}
graphql
# Union : types complètement différents sans champs communs garantis (contrairement à interface)
union ResultatRecherche = Produit | Categorie | Utilisateur

type Query {
  rechercheGlobale(terme: String!): [ResultatRecherche!]!
}
graphql
# Une union exige TOUJOURS des fragments inline (aucun champ commun n'est présumé)
query {
  rechercheGlobale(terme: "clavier") {
    __typename   # méta-champ indispensable pour savoir quel type a été retourné
    ... on Produit {
      nom
      prix
    }
    ... on Categorie {
      nom
      description
    }
    ... on Utilisateur {
      nom
      email
    }
  }
}
python
# Déclaration Strawberry : interface, union et enum
import strawberry
from enum import Enum
from typing import Union

@strawberry.enum
class StatutCommande(Enum):
    EN_ATTENTE = "en_attente"
    EXPEDIEE = "expediee"

@strawberry.interface
class Notification:
    id: strawberry.ID
    lu: bool

@strawberry.type
class NotificationCommande(Notification):
    commande_id: strawberry.ID

ResultatRecherche = strawberry.union("ResultatRecherche", (Produit, Categorie))

Résumé

  • Interface : les types implémentants PARTAGENT des champs communs garantis (id, lu, ...).
  • Union : les types membres n'ont AUCUN champ commun garanti, __typename est indispensable côté client.
  • Enum : validé côté serveur, empêche l'envoi/retour de valeurs hors de l'ensemble défini.
  • Les fragments inline (... on Type) sont obligatoires pour accéder aux champs spécifiques d'une interface/union.

Exercices pratiques

1 disponible
1

Mission : modéliser un centre de notifications hétérogène

Objectif : Choisir entre interface et union selon que les types partagent ou non un socle commun, et garantir que le client peut toujours identifier le type reçu.

Contexte

Le centre de notifications de l'application doit afficher, dans une même liste, des notifications de commande expédiée et des notifications de promotion. La barre de recherche globale, elle, doit mélanger des produits, des catégories et des utilisateurs — des types qui n'ont aucun champ en commun. Le tech lead te demande de choisir le bon outil de modélisation pour chaque cas, puis de coder le premier.

Résoudre l’exercice →