backend / graphql
Types complexes : interfaces, unions, enums
Explication
Ce que vous allez apprendre
- Déclarer un
enumet comprendre pourquoi il est plus sûr qu'une simple chaîne de caractères - Définir une
interfacepour garantir un socle de champs communs à plusieurs types - Définir une
unionpour 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
__typenameet 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.
| Construction | Champs garantis en commun ? | Cas d'usage typique |
|---|---|---|
enum | — (valeur unique parmi un ensemble fermé) | Statuts, catégories fermées de valeurs |
interface | Oui, un socle partagé obligatoire | Notifications, éléments d'un fil d'actualité |
union | Non, aucun champ commun garanti | Ré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
# 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!]!
}# Interrogation d'une interface avec fragments inline pour les champs spécifiques
query {
notifications {
id
lu
... on NotificationCommande {
commande { id statut }
}
... on NotificationPromo {
pourcentageReduction
}
}
}# Union : types complètement différents sans champs communs garantis (contrairement à interface)
union ResultatRecherche = Produit | Categorie | Utilisateur
type Query {
rechercheGlobale(terme: String!): [ResultatRecherche!]!
}# 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
}
}
}# 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,
__typenameest 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
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.