In dit artikel presenteer ik het gebruik van polymorfisme in GraphQL. Voordat ik begin, is het echter de moeite waard om in herinnering te brengen wat polymorfisme en GraphQL zijn.
Polymorfisme
Polymorfisme is een belangrijk onderdeel van objectgeoriënteerd programmeren. Om de zaken te vereenvoudigen, is het gebaseerd op het feit dat objecten van verschillende klassen toegang hebben tot dezelfde interface, wat betekent dat we van elk van hen dezelfde functionaliteit kunnen verwachten, maar niet noodzakelijk op dezelfde manier geïmplementeerd. In Ruby ontwikkelaars kunnen verkrijgen polymorfisme op drie manieren:
Erfenis
Erfenis bestaat uit het maken van een ouderklasse en kindklassen (d.w.z. die overerven van de ouderklasse). Subklassen krijgen de functionaliteit van de ouderklasse en stellen je ook in staat om functionaliteit te wijzigen of toe te voegen.
Voorbeeld:
klasse Document
attr_lezer :naam
einde
klasse PDFDocument < Document
def uitbreiding
:pdf
einde
einde
klasse ODTDocument < Document
Uitbreiding
:odt
einde
einde
Modules
Modules in Ruby hebben veel toepassingen. Een daarvan is mixins (lees meer over mixins in De ultieme onderverdeling: Ruby vs. Python). Mixins in Ruby kunnen op dezelfde manier gebruikt worden als interfaces in andere programmeertalen (bijvoorbeeld in Java), bijvoorbeeld, kun je daarin methoden definiëren die gemeenschappelijk zijn voor objecten die een bepaalde mixin zullen bevatten. Het is een goede gewoonte om alleen-lezen methoden in modules op te nemen, dus methoden die de toestand van dit object niet wijzigen.
Voorbeeld:
module Belastbaar
def. belasting
prijs * 0,23
einde
einde
Klasse Auto
inclusief belastbaar
attr_lezer :prijs
einde
Klasse Boek
include belastingplichtige
attr_lezer :prijs
einde
Typen met eend
Dit is een van de belangrijkste eigenschappen van dynamisch getypeerde talen. De naam komt van de beroemde test als het eruit ziet als een eend, zwemt als een eend en kwaakt als een eend, dan is het waarschijnlijk een eend. De programmeur hoeft niet geïnteresseerd te zijn tot welke klasse het gegeven object behoort. Wat belangrijk is, zijn de methoden die op dit object kunnen worden aangeroepen.
Gebruik de klassen die in het bovenstaande voorbeeld zijn gedefinieerd:
Klasse Auto
attr_lezer :prijs
def initialiseer(prijs)
@prijs = prijs
einde
einde
Klasse Boek
attr_lezer :prijs
def initialiseer(prijs)
@prijs = prijs
einde
einde
auto = auto.nieuw(20.0)
boek = boek.nieuw(10.0)
[auto, boek].map(&:prijs
GrapQL
GraphQL is een relatief nieuwe querytaal voor API's. De voordelen zijn onder andere dat het een zeer eenvoudige syntaxis heeft en bovendien bepaalt de klant wat hij precies wil krijgen, omdat elke klant precies krijgt wat hij wil en niets anders.
Dit is waarschijnlijk alles wat we op dit moment moeten weten. Laten we ter zake komen.
Presentatie van het probleem
Om het probleem en de oplossing zo goed mogelijk te begrijpen, laten we een voorbeeld maken. Het zou goed zijn als het voorbeeld zowel origineel als redelijk nuchter was. Een voorbeeld dat ieder van ons op een dag kan tegenkomen. Wat dacht je van... dieren? Ja! Goed idee!
Stel dat we een back-end applicatie hebben die geschreven is in Ruby on Rails. Het is al aangepast aan het bovenstaande schema. Laten we ook aannemen dat we al GraphQL geconfigureerd. We willen de klant in staat stellen een aanvraag te doen binnen de volgende structuur:
{
allZoos : {
zoo: {
naam
stad
dieren: {
...
}
}
}
}
Wat er in plaats van de drie puntjes moet worden gezet om de ontbrekende informatie te krijgen - daar komen we later wel achter.
Implementatie
Hieronder presenteer ik de stappen die nodig zijn om het doel te bereiken.
Een query toevoegen aan QueryType
Eerst moet je definiëren wat de query allZoos precies betekent. Hiervoor moeten we naar het bestandapp/graphql/types/query_type.rb en definieer de query:
module Types
klasse QueryType < Types::BaseObject
veld :all_zoos, [Types::ZooType], null: false
def alle_zoös
Dierentuin.alle
einde
einde
einde
De query is al gedefinieerd. Nu is het tijd om de retoursoorten te definiëren.
Definitie van soorten
Het eerste type dat nodig is, is ZooType. Laten we het definiëren in het bestand app/graphql/types/ zoo_type.rb:
Nu is het tijd om het type AnimalType te definiëren:
module Typen
Klasse DierType < Types::BaseUnion
mogelijke_typen OlifantType, KatType, HondType
def self.resolve_type(obj, ctx)
als obj.is_een?(Olifant)
OlifantType
als obj.is_een?(Kat)
CatType
anders indien obj.is_een?(Hond)
HondType
eind
einde
einde
einde
We moeten een lijst maken van alle typen die een bepaalde unie kunnen vormen. 3.We overschrijven de functie self.resolve_object(obj, ctx),die het type van een gegeven object moet teruggeven.
De volgende stap is het definiëren van de soorten dieren. We weten echter dat sommige velden gemeenschappelijk zijn voor alle dieren. Laten we ze opnemen in het type AnimalInterface:
module Typen
module Dierlijke interface
include Types::BaseInterface
veld :name, String, null: false
veld :leeftijd, geheel getal, nul: vals
einde
einde
Met deze interface kunnen we de soorten specifieke dieren definiëren:
module Types
Klasse OlifantType < Types::BaseObject
implementeert Types::AnimalInterface
veld :slurf_lengte, Vlotter, nul: vals
einde
einde
module Types
Klasse Kattype < Types::BaseObject
implementeert Types::AnimalInterface
veld :hair_type, String, null: false
einde
einde
module Types
Klasse HondType < Types::BaseObject
implementeert Types::AnimalInterface
veld :ras, String, nul: vals
einde
einde
Dat is het! Klaar! Nog een laatste vraag: hoe kunnen we gebruiken wat we aan de kant van de klant hebben gedaan?
De query bouwen
{
allZoos : {
zoo: {
naam
stad
dieren: {
__typenaam
... op ElephantType {
naam
leeftijd
trunkLength
}
... op CatType {
naam
age
hairType
}
... on DogType {
naam
age
ras
}
}
}
}
}
We kunnen hier een extra __typename veld gebruiken, dat het exacte type van een gegeven element teruggeeft (bijvoorbeeld CatType). Hoe ziet een voorbeeldantwoord eruit?
Eén nadeel van deze aanpak is duidelijk. In de query moeten we bij elk type de naam en leeftijd invoeren, ook al weten we dat alle dieren deze velden hebben. Dit is niet hinderlijk als de verzameling compleet verschillende objecten bevat. In dit geval delen de dieren echter bijna alle velden. Kan dit op de een of andere manier worden verbeterd?
Natuurlijk! We maken de eerste wijziging in het bestand app/graphql/types/zoo_type.rb:
We hebben de unie die we eerder hebben gedefinieerd niet langer nodig. We veranderen Types::AnimalType naar Types::AnimalInterface.
De volgende stap is het toevoegen van een functie die een type teruggeeft van Typen :: AnimalInterface en voeg ook een lijst met orphan_types toe, dus types die nooit direct gebruikt worden:
module Typen
module Dierlijke interface
include Types::BaseInterface
veld :name, String, null: false
veld :leeftijd, geheel getal, nul: vals
definitie_methoden doen
def resolve_type(obj, ctx)
als obj.is_een?(Olifant)
OlifantType
als obj.is_een?(Kat)
CatType
als obj.is_een?(Hond)
HondType
eind
einde
einde
orphan_types Types::ElephantType, Types::CatType, Types::DogType
eind
einde
Dankzij deze eenvoudige procedure heeft de query een minder complexe vorm:
{
allZoos : {
zoo: {
naam
stad
dieren: {
__typenaam
naam
leeftijd
... op ElephantType {
slurflengte
}
... op CatType {
hairType
}
... op DogType {
ras
}
}
}
}
}
Samenvatting
GraphQL is echt een geweldige oplossing. Als je het nog niet kent, probeer het dan. Geloof me, het is de moeite waard. Het werkt geweldig bij het oplossen van problemen die zich voordoen in bijvoorbeeld REST API's. Zoals ik hierboven heb laten zien, polymorfisme is niet echt een obstakel. Ik heb twee methoden gepresenteerd om het aan te pakken. Herinnering:
Als je werkt op een lijst van objecten met een gemeenschappelijke basis of een gemeenschappelijke interface - gebruik dan de interfaces,
Als je werkt op een lijst van objecten met een andere structuur, gebruik dan een andere interface - gebruik de unie