In diesem Artikel werde ich die Verwendung von Polymorphismus in GraphQL vorstellen. Bevor ich jedoch damit beginne, sollte man sich in Erinnerung rufen, was Polymorphismus und GraphQL sind.
Polymorphismus
Polymorphismus ist eine Schlüsselkomponente von objektorientierte Programmierung. Der Einfachheit halber basiert sie auf der Tatsache, dass Objekte verschiedener Klassen Zugriff auf dieselbe Schnittstelle haben, was bedeutet, dass wir von jeder von ihnen dieselbe Funktionalität erwarten können, die aber nicht unbedingt auf dieselbe Weise implementiert ist. In Ruby-Entwickler erhalten können Polymorphismus auf drei Arten:
Vererbung
Vererbung besteht darin, eine übergeordnete Klasse und untergeordnete Klassen (d. h., die von der übergeordneten Klasse erben) zu erstellen. Unterklassen erhalten die Funktionalität der übergeordneten Klasse und ermöglichen es Ihnen auch, eine Funktionalität zu ändern und hinzuzufügen.
Beispiel:
Klasse Dokument
attr_leser :name
end
class PDFDocument < Dokument
def extension
:pdf
end
end
class ODTDocument < Dokument
def Erweiterung
:odt
end
end
Module
Module in Rubinrot haben viele Verwendungsmöglichkeiten. Eine davon sind Mixins (lesen Sie mehr über Mixins in Die ultimative Aufschlüsselung: Ruby vs. Python). Mixins können in Ruby ähnlich wie Schnittstellen in anderen Programmiersprachen verwendet werden (z. B. in Java) können Sie zum Beispiel Methoden definieren, die allen Objekten gemeinsam sind, die ein bestimmtes Mixin enthalten werden. Es ist eine gute Praxis, schreibgeschützte Methoden in Module aufzunehmen, also Methoden, die den Zustand des Objekts nicht verändern.
Beispiel:
Modul steuerpflichtig
Def-Steuer
Preis * 0,23
end
end
Klasse Auto
include steuerpflichtig
attr_leser :preis
end
class Buch
include steuerpflichtig
attr_leser :preis
end
Ente tippen
Dies ist eine der wichtigsten Eigenschaften dynamisch typisierter Sprachen. Der Name stammt von dem berühmten Test: Wenn es wie eine Ente aussieht, wie eine Ente schwimmt und wie eine Ente quakt, dann ist es wahrscheinlich eine Ente. Die Programmierer muss sich nicht dafür interessieren, zu welcher Klasse das gegebene Objekt gehört. Wichtig sind die Methoden, die auf diesem Objekt aufgerufen werden können.
Verwenden Sie die im obigen Beispiel definierten Klassen:
Klasse Auto
attr_leser :preis
def initialize(preis)
@Preis = Preis
end
end
class Buch
attr_leser :preis
def initialize(preis)
@Preis = Preis
end
end
Auto = Auto.neu(20.0)
Buch = Buch.neu(10.0)
[Auto, Buch].map(&:Preis
GrapQL
GraphQL ist eine relativ neue Abfragesprache für APIs. Zu ihren Vorteilen gehört die Tatsache, dass sie eine sehr einfache Syntax hat und außerdem der Kunde entscheidet, was genau er erhalten möchte, da jeder Kunde genau das bekommt, was er möchte und nichts anderes.
Das ist wahrscheinlich alles, was wir im Moment wissen müssen. Also, kommen wir zur Sache.
Darstellung des Problems
Um das Problem und seine Lösung besser zu verstehen, sollten wir uns ein Beispiel ausdenken. Es wäre gut, wenn das Beispiel sowohl originell als auch ziemlich bodenständig wäre. Eines, das jedem von uns eines Tages begegnen kann. Wie wäre es mit... Tieren? Ja! Tolle Idee!
Angenommen, wir haben eine Backend-Anwendung, die in Ruby on Rails. Sie ist bereits an das obige Schema angepasst. Nehmen wir außerdem an, dass wir bereits über GraphQL konfiguriert. Wir möchten dem Kunden die Möglichkeit geben, eine Anfrage in der folgenden Struktur zu stellen:
Was anstelle der drei Punkte zu setzen ist, um die fehlenden Informationen zu erhalten, werden wir später herausfinden.
Umsetzung
Im Folgenden werde ich die zur Erreichung dieses Ziels erforderlichen Schritte erläutern.
Hinzufügen einer Abfrage zu QueryType
Zunächst muss definiert werden, was genau die Abfrage allZoos bedeutet. Zu diesem Zweck müssen wir die Dateiapp/graphql/types/query_type.rb und definieren Sie die Abfrage:
Modul Typen
Klasse QueryType < Types::BaseObject
Feld :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.alle
end
end
end
Die Abfrage ist bereits definiert. Nun ist es an der Zeit, die Rückgabetypen zu definieren.
Definition der Typen
Der erste erforderliche Typ ist ZooType. Definieren wir ihn in der Datei app/graphql/types/ zoo_type.rb:
Modul Typen
Klasse ZooType < Typen::BaseObject
Feld :name, String, null: false
feld :stadt, String, null: false
feld :tiere, [Typen::TierTyp], null: false
end
end
Nun ist es an der Zeit, den Typ AnimalType zu definieren:
Modul Typen
Klasse AnimalType < Types::BaseUnion
mögliche_Typen ElefantTyp, KatzeTyp, HundTyp
def self.resolve_type(obj, ctx)
if obj.is_a?(Elefant)
ElefantTyp
elsif obj.is_a?(Katze)
CatType
elsif obj.is_a?(Hund)
HundTyp
end
end
end
end
Wir müssen alle Typen auflisten, die eine bestimmte Gewerkschaft bilden können. 3. wir überschreiben die Funktion self.resolve_object(obj, ctx),die den Typ eines bestimmten Objekts zurückgeben muss.
Der nächste Schritt besteht darin, die Tierarten zu definieren. Wir wissen jedoch, dass einige Felder für alle Tiere gleich sind. Nehmen wir sie in den Typ AnimalInterface auf:
Modul Typen
modul AnimalInterface
include Typen::BaseInterface
feld :name, String, null: false
feld :alter, Ganzzahl, null: false
end
end
Anhand dieser Schnittstelle können wir nun die einzelnen Tierarten definieren:
Modul Typen
Klasse ElefantTyp < Types::BaseObject
implementiert Types::AnimalInterface
Feld :trunk_length, Float, null: false
end
end
Modul Typen
Klasse CatType < Types::BaseObject
implementiert Types::AnimalInterface
Feld :hair_type, String, null: false
end
end
Modul Typen
Klasse DogType < Types::BaseObject
implementiert Types::AnimalInterface
Feld :Rasse, String, null: false
end
end
Das war's! Fertig! Eine letzte Frage: Wie können wir das, was wir auf der Kundenseite gemacht haben, nutzen?
Erstellung der Abfrage
{
allZoos : {
zoo: {
Name:
Stadt:
Tiere: {
__typenname
... on ElefantTyp {
Name
Alter
rüsselLänge
}
... on CatType {
Name
Alter
hairType
}
... on DogType {
Name
Alter
Rasse
}
}
}
}
}
Wir können hier ein zusätzliches __typename-Feld verwenden, das den genauen Typ eines bestimmten Elements zurückgibt (z. B. CatType). Wie sieht eine Beispielantwort aus?
Ein Nachteil dieses Ansatzes ist offensichtlich. In der Abfrage müssen wir bei jedem Typ den Namen und das Alter eingeben, obwohl wir wissen, dass alle Tiere diese Felder haben. Dies ist nicht weiter störend, wenn die Sammlung völlig unterschiedliche Objekte enthält. In diesem Fall haben die Tiere jedoch fast alle Felder gemeinsam. Kann das irgendwie verbessert werden?
Ja, natürlich! Wir nehmen die erste Änderung in der Datei vor app/graphql/types/zoo_type.rb:
Modul Typen
Klasse ZooType < Typen::BaseObject
Feld :name, String, null: false
feld :stadt, String, null: false
feld :tiere, [Types::AnimalInterface], null: false
end
end
Wir brauchen nicht mehr die Vereinigung, die wir vorher definiert haben. Wir ändern Typen::AnimalType zu Typen::AnimalInterface.
Der nächste Schritt besteht darin, eine Funktion hinzuzufügen, die einen Typ von Typen :: AnimalInterface und fügen Sie auch eine Liste von orphan_types hinzu, also Typen, die nie direkt verwendet werden:
Modul Typen
modul AnimalInterface
include Typen::BaseInterface
feld :name, String, null: false
feld :alter, Ganzzahl, null: false
definition_methods do
def resolve_type(obj, ctx)
if obj.is_a?(Elefant)
ElefantTyp
elsif obj.is_a?(Katze)
CatType
elsif obj.is_a?(Hund)
HundTyp
end
end
end
orphan_types Typen::ElefantTyp, Typen::KatzeTyp, Typen::HundTyp
end
end
Dank dieses einfachen Verfahrens hat die Abfrage eine weniger komplexe Form:
{
allZoos : {
zoo: {
Name:
Stadt:
Tiere: {
__typenname
Name
Alter
... on ElefantTyp {
RüsselLänge
}
... on CatType {
HaarTyp
}
... on DogType {
Rasse
}
}
}
}
}
Zusammenfassung
GraphQL ist eine wirklich großartige Lösung. Wenn Sie sie noch nicht kennen, probieren Sie sie aus. Glauben Sie mir, das ist es wert. Sie leistet großartige Arbeit bei der Lösung von Problemen, die z. B. bei REST-APIs auftreten. Wie ich oben gezeigt habe, Polymorphismus ist kein wirkliches Hindernis für sie. Ich habe zwei Methoden vorgestellt, um das Problem zu lösen. Zur Erinnerung:
Wenn Sie mit einer Liste von Objekten mit einer gemeinsamen Basis oder einer gemeinsamen Schnittstelle arbeiten, verwenden Sie die Schnittstellen,
Wenn Sie mit einer Liste von Objekten mit einer anderen Struktur arbeiten, verwenden Sie eine andere Schnittstelle - verwenden Sie die Union