Í þessari grein mun ég kynna notkun margforms í GraphQL. Áður en ég byrja er þó vert að rifja upp hvað margform og GraphQL eru.
Hér er tómt.
Mörgformun
Mörgformun er lykilþáttur í hlutbundin forritun. Til að einfalda hlutina er þetta byggt á þeirri staðreynd að hlutir úr mismunandi flokkum hafa aðgang að sama viðmóti, sem þýðir að við getum búist við sömu virkni frá hverjum þeirra, en ekki endilega að hún sé innleidd á sama hátt. Í Rúbín forritarar geta fengið fjölbreytileiki á þrjá vegu:
Arfleifð
Arfleifð Felið í sér að búa til foreldurklas og undirklasa (þ.e. erfa frá foreldurklasnum). Undirklasar fá virkni foreldurklasins og gera þér kleift að breyta henni og bæta við nýrri virkni.
Dæmi:
class Document
attr_reader :name
end
class PDFDocument < Document
def extension
:pdf
end
end
class ODTDocument < Document
def extension
:odt
end
end
Einingar
Modúl í Rúbín Eiga margvíslega notkun. Ein þeirra er mixins (lestu meira um mixins í Endanleg sundurliðun: Ruby vs. Python). Mixins í Ruby má nota á svipaðan hátt og viðmót í öðrum forritunarmál (t.d. í Java), til dæmis geturðu skilgreint í þeim aðferðir sem sameiginlegar eru fyrir hluti og sem innihalda ákveðinn mixin. Það er gott vinnubrögð að setja eingöngu lestraraðferðir í einingar, þ.e. aðferðir sem breyta ekki ástandi þessa hlutar.
Dæmi:
module Taxable
def tax
price * 0.23
end
end
class Car
include Taxable
attr_reader :price
end
class Book
include Taxable
attr_reader :price
end
Öndartýpu
Þetta er ein af lykilatriðum dýnamískra forritunarmála. Nafnið kemur frá hinum fræga prófi: ef það lítur út eins og önd, syndir eins og önd og kvakar eins og önd, þá er það líklega önd. forritari Þarf ekki að hafa áhuga á því hvaða flokki gefið hlutinn tilheyrir. Það sem skiptir máli eru aðferðirnar sem hægt er að kalla á þennan hlut.
Með því að nota klasana sem skilgreindir eru í dæminu hér að ofan:
class Car
attr_reader :price
def initialize(price)
@price = price
end
end
class Book
attr_reader :price
def initialize(price)
@price = price
end
end
car = Car.new(20.0)
book = Book.new(10.0)
[car, book].map(&:price)
GrapQL
GraphQL er tiltölulega nýtt fyrirspurnarmál fyrir API. Kostir þess felast í því að það hefur mjög einfalda málfræði og auk þess ákveður viðskiptavinurinn nákvæmlega hvað hann vill fá, þannig að hver viðskiptavinur fær nákvæmlega það sem hann vill og ekkert annað.
Þetta er líklega allt sem við þurfum að vita um þessar mundir. Svo skulum við koma að kjarna málsins.
Kynning á vandamálinu
Til að skilja vandamálið og lausnina sem best skulum við búa til dæmi. Gott væri ef dæmið væri bæði frumlegt og nokkuð jarðtengt. Eitt sem hver og einn okkur getur rekist á einhvern daginn. Hvað með… dýr? Já! Frábær hugmynd!
Segjum að við höfum bakendaforrit skrifað í Ruby on Rails. Það er þegar aðlagað til að takast á við ofangreinda áætlun. Látum okkur einnig gera ráð fyrir að við höfum þegar GraphQL stillt. Við viljum gera viðskiptavininum kleift að senda fyrirspurn innan eftirfarandi uppbyggingar:
{
allZoos : {
zoo: {
name
city
animals: {
...
}
}
}
}
Hvað eigi að setja í staðinn fyrir þrjá punkta til að fá fram vantar upplýsingar – það komumst við að síðar.
Innleiðing
Hér að neðan mun ég kynna skrefin sem þarf til að ná markmiðinu.
Bæta fyrirspurn við QueryType
Í fyrsta lagi þarftu að skilgreina nákvæmlega hvað fyrirspurnin allZoos þýðir. Til þess þurfum við að opna skrána.app/graphql/types/query_type.rb og skilgreina fyrirspurnina:
Gerðir eininga
class QueryType < Types::BaseObject
field :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.all
end
end
end
Fyrirspurnin er þegar skilgreind. Nú er kominn tími til að skilgreina skilartegundirnar.
Skilgreining gerða
Fyrsta tegundin sem þarf verður ZooType. Skulum skilgreina hana í skránni. app/graphql/types/ zoo_type.rb:
Gerðir eininga
class ZooType < Types::BaseObject
field :name, String, null: false
field :city, String, null: false
field :animals, [Types::AnimalType], null: false
end
end
Nú er kominn tími til að skilgreina gerðina 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)
CatType
elsif obj.is_a?(Dog)
DogType
end
end
end
end
Við verðum að telja upp allar tegundir sem geta myndað tiltekið samband. 3. Við skrifum yfir fallið leysir sjálfan sig sem hlut í samhengi,sem verður að skila tegund tiltekins hlutar.
Næsta skref er að skilgreina tegundir dýra. Hins vegar vitum við að sumir eiginleikar eru sameiginlegir öllum dýrum. Skulum bæta þeim við í gerðina AnimalInterface:
Gerðir módula
module AnimalInterface
include Types::BaseInterface
field :name, String, null: false
field :age, Integer, null: false
end
end
Með þessu viðmóti getum við haldið áfram að skilgreina tegundir einstakra dýra:
module Types
class ElephantType < Types::BaseObject
implements Types::AnimalInterface
field :trunk_length, Float, null: false
end
end
module Types
class CatType < Types::BaseObject
implements Types::AnimalInterface
field :hair_type, String, null: false
end
end
module Types
class DogType < Types::BaseObject
implements Types::AnimalInterface
field :breed, String, null: false
end
end
Þetta er það! Tilbúið! Ein síðasta spurning: hvernig getum við nýtt það sem við höfum gert frá hlið viðskiptavinarins?
Að byggja fyrirspurnina
{
allZoos : {
zoo: {
name
city
animals: {
__typename
... on ElephantType {
name
age
trunkLength
}
... á CatType {
nafn
aldur
hárgerð
}
... á DogType {
nafn
aldur
tegund
}
}
}
Við getum notað viðbótar __typename-reiti hér, sem skilar nákvæmri tegund tiltekins þáttar (t.d. CatType). Hvernig myndi sýnissvar líta út?
Einn ókostur þessarar nálgunar er augljós. Í fyrirspurninni verðum við að slá inn nafn og aldur í hverri tegund, þrátt fyrir að við vitum að öll dýrin hafi þessi reiti. Þetta er ekki vandamál þegar safnið inniheldur algjörlega ólík fyrirbæri. Í þessu tilfelli deila dýrin hins vegar nánast öllum reitum. Er hægt að bæta þetta einhvern veginn?
Auðvitað! Við gerum fyrstu breytinguna í skránni. app/graphql/types/zoo_type.rb:
Gerðir eininga
class ZooType < Types::BaseObject
field :name, String, null: false
field :city, String, null: false
field :animals, [Types::AnimalInterface], null: false
end
end
Við þurfum ekki lengur þá sambandsaðgerð sem við höfum áður skilgreint. Við breytum Gerðir::Dýrategund til Gerðir::Dýraviðmót.
Næsta skref er að bæta við falli sem skilar tegund frá Gerðir :: Dýraviðmót og bæta einnig við lista yfir orphan_types, þ.e. gerðir sem aldrei eru notaðar beint:
module Types
module AnimalInterface
include Types::BaseInterface
field :name, String, null: false
field :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)
DogType
end
end
end
orphan_types Types::ElephantType, Types::CatType, Types::DogType
end
end
Þökk sé þessari einföldu aðferð hefur fyrirspurnin minna flókna mynd:
{
allZoos : {
zoo: {
name
city
animals: {
__typename
name
age
... on ElephantType {
trunkLength
}
... on CatType {
hairType
}
... á Hundategund {
tegund
}
}
}
}
}
Yfirlit
GraphQL er virkilega frábær lausn. Ef þú þekkir hana ekki enn, prófaðu hana. Treystu mér, það er þess virði. Hún leysir frábærlega vandamál sem koma upp, til dæmis í REST-API-um. Eins og ég sýndi hér að ofan, fjölbreytileiki er ekki raunveruleg hindrun fyrir það. Ég kynnti tvær aðferðir til að takast á við það. Áminning:
Ef þú vinnur með lista yfir hluti sem deila sameiginlegum undirstöðu eða sameiginlegu viðmóti – notaðu viðmótin.,
Ef þú vinnur með lista yfir hluti með annan uppbyggingu, notaðu annað viðmót – notaðu union.