Dans cet article, je vais présenter l'utilisation du polymorphisme dans GraphQL. Mais avant de commencer, il convient de rappeler ce que sont le polymorphisme et GraphQL.
Polymorphisme
Polymorphisme est un élément clé de la la programmation orientée objet. Pour simplifier, elle est basée sur le fait que des objets de classes différentes ont accès à la même interface, ce qui signifie que l'on peut attendre de chacun d'eux la même fonctionnalité, mais pas nécessairement implémentée de la même manière. En Développeurs Ruby peut obtenir polymorphisme de trois manières :
Héritage
Héritage consiste à créer une classe mère et des classes enfants (c'est-à-dire héritant de la classe mère). Les sous-classes reçoivent les fonctionnalités de la classe mère et permettent également de modifier et d'ajouter une fonctionnalité.
Exemple :
classe Document
attr_reader :name
fin
classe PDFDocument < Document
def extension
:pdf
end
fin
classe ODTDocument < Document
def extension
:odt
end
fin
Modules
Modules en Rubis ont de nombreuses utilisations. L'une d'entre elles est le mixage (pour en savoir plus sur les mixages, consultez la rubrique L'analyse ultime : Rubis vs. Python). Les mixins en Ruby peuvent être utilisés de la même manière que les interfaces dans d'autres langages de programmation (par exemple, en Java), par exemple, vous pouvez y définir des méthodes communes aux objets qui contiendront un mixin donné. Il est conseillé d'inclure des méthodes en lecture seule dans les modules, c'est-à-dire des méthodes qui ne modifieront pas l'état de cet objet.
Exemple :
module Taxable
def tax
prix * 0,23
fin
fin
classe Voiture
include Taxable
attr_reader :prix
end
classe Livre
include Taxable
attr_reader :prix
end
Dactylographie du canard
C'est l'une des principales caractéristiques des langages à typage dynamique. Le nom vient du célèbre test "si ça ressemble à un canard, si ça nage comme un canard et si ça jacasse comme un canard, alors c'est probablement un canard". Le programmeur ne doit pas s'intéresser à la classe à laquelle appartient l'objet donné. Ce qui est important, ce sont les méthodes qui peuvent être appelées sur cet objet.
En utilisant les classes définies dans l'exemple ci-dessus :
classe Voiture
attr_reader :prix
def initialize(prix)
@price = prix
end
end
classe Book
attr_reader :prix
def initialize(prix)
@price = prix
end
end
car = Car.new(20.0)
book = Book.new(10.0)
[car, book].map(&:price
GrapQL
GraphQL est un langage de requête relativement nouveau pour les API. Il présente l'avantage d'avoir une syntaxe très simple et, en outre, le client décide de ce qu'il veut obtenir exactement, car chaque client reçoit exactement ce qu'il veut et rien d'autre.
C'est probablement tout ce que nous avons besoin de savoir pour l'instant. Alors, venons-en au fait.
Présentation du problème
Pour mieux comprendre le problème et sa solution, créons un exemple. Il serait bon que cet exemple soit à la fois original et assez terre à terre. Un exemple que chacun d'entre nous peut rencontrer un jour. Pourquoi pas... des animaux ? Oui ! Excellente idée !
Supposons que nous ayons une application dorsale écrite en Ruby on Rails. Il est déjà adapté pour gérer le schéma ci-dessus. Supposons également que nous ayons déjà GraphQL configuré. Nous voulons permettre au client de faire une demande dans la structure suivante :
{
allZoos : {
zoo : {
nom
ville
animaux : {
...
}
}
}
}
Nous verrons plus tard ce qu'il faut mettre à la place des trois points pour obtenir l'information manquante.
Mise en œuvre
Je présenterai ci-dessous les étapes nécessaires pour atteindre l'objectif.
Ajout d'une requête à QueryType
Tout d'abord, vous devez définir ce que signifie exactement la requête allZoos. Pour ce faire, nous devons visiter le fichierapp/graphql/types/query_type.rb et définir la requête :
module Types
class QueryType < Types::BaseObject
field :all_zoos, [Types::ZooType], null : false
def all_zoos
Zoo.all
end
end
end
La requête est déjà définie. Il est maintenant temps de définir les types de retour.
Définition des types
Le premier type requis sera ZooType. Définissons-le dans le fichier app/graphql/types/ zoo_type.rb:
module Types
class ZooType < Types::BaseObject
champ :name, Chaîne, null : false
champ :city, Chaîne, null : false
champ :animals, [Types::AnimalType], null : false
end
end
Il est maintenant temps de définir le type AnimalType :
module Types
class AnimalType < Types::BaseUnion
possible_types ElephantType, CatType, DogType
def self.resolve_type(obj, ctx)
if obj.is_a ?(Elephant)
ElephantType
elsif obj.is_a ?(Cat)
Type de chat
elsif obj.is_a ?(Dog)
Type Chien
fin
fin
end
fin
Nous devons énumérer tous les types qui peuvent composer un syndicat donné. 3. nous remplaçons la fonction self.resolve_object(obj, ctx),qui doit renvoyer le type d'un objet donné.
L'étape suivante consiste à définir les types d'animaux. Cependant, nous savons que certains champs sont communs à tous les animaux. Incluons-les dans le type AnimalInterface :
module Types
module AnimalInterface
include Types::BaseInterface
champ :name, String, null : false
field :age, Integer, null : false
end
end
Avec cette interface, nous pouvons définir les types d'animaux spécifiques :
module Types
class ElephantType < Types::BaseObject
implémente Types::AnimalInterface
champ :trunk_length, Float, null : false
end
fin
module Types
class CatType < Types::BaseObject
implémente Types::AnimalInterface
champ :hair_type, String, null : false
end
fin
module Types
class DogType < Types::BaseObject
implémente Types::AnimalInterface
champ :breed, String, null : false
end
end
Ça y est ! Vous êtes prêts ! Une dernière question : comment pouvons-nous utiliser ce que nous avons fait du côté du client ?
Construire la requête
{
allZoos : {
zoo : {
nom
ville
animals : {
__typename
... on ElephantType {
nom
âge
longueur du tronc
}
... on CatType {
nom
âge
hairType
}
... on DogType {
nom
âge
race
}
}
}
}
}
Nous pouvons utiliser un champ __typename supplémentaire ici, qui renverra le type exact d'un élément donné (par exemple, CatType). À quoi ressemblera un exemple de réponse ?
L'inconvénient de cette approche est évident. Dans la requête, nous devons saisir le nom et l'âge pour chaque type, même si nous savons que tous les animaux possèdent ces champs. Cela n'est pas gênant lorsque la collection contient des objets complètement différents. Dans ce cas, cependant, les animaux partagent presque tous les champs. Peut-on l'améliorer d'une manière ou d'une autre ?
Bien sûr ! Nous effectuons la première modification dans le fichier app/graphql/types/zoo_type.rb:
module Types
class ZooType < Types::BaseObject
champ :name, String, null : false
champ :city, Chaîne, null : false
champ :animals, [Types::AnimalInterface], null : false
end
end
Nous n'avons plus besoin de l'union que nous avions définie auparavant. Nous changeons Types::AnimalType à Types::AnimalInterface.
L'étape suivante consiste à ajouter une fonction qui renvoie un type de Types : : AnimalInterface et ajoute également une liste de types orphelins, c'est-à-dire de types qui ne sont jamais utilisés directement :
module Types
module AnimalInterface
include Types::BaseInterface
champ :name, String, null : false
champ :age, Integer, null : false
definition_methods do
def resolve_type(obj, ctx)
if obj.is_a ?(Elephant)
ElephantType
elsif obj.is_a ?(Cat)
CatType
elsif obj.is_a ?(Dog)
Type Chien
end
fin
fin
orphan_types Types::ElephantType, Types::CatType, Types::DogType
fin
fin
Grâce à cette procédure simple, la requête a une forme moins complexe :
{
allZoos : {
zoo : {
nom
ville
animals : {
__typename
nom
âge
... on ElephantType {
longueur du tronc
}
... on CatType {
hairType
}
... on DogType {
race
}
}
}
}
}
Résumé
GraphQL est une très bonne solution. Si vous ne la connaissez pas encore, essayez-la. Croyez-moi, cela en vaut la peine. Elle permet de résoudre les problèmes apparaissant, par exemple, dans les API REST. Comme je l'ai montré plus haut, polymorphisme n'est pas un véritable obstacle. J'ai présenté deux méthodes pour l'aborder. Rappel :
Si vous opérez sur une liste d'objets ayant une base commune ou une interface commune, utilisez les interfaces,
Si vous opérez sur une liste d'objets ayant une structure différente, utilisez une interface différente - utilisez l'union