Professional Documents
Culture Documents
HomeAbout
FirstThoughtsonGraphQL
ReactJSConfjustwrappeduplastweekandthevideosstartedbeingreleasedovertheweekend.React
Nativegotalotofbuzz,forextremelygoodreason,butequallyasamazingwasthesessiononGraphQLby
DanielSchaferandJingChen.
InanutshellGraphQLallowsyoutoquerytheentirestateofyourapplicationinonego.Thedataisreturned
intheminimalexactshapeyourequest.Byitselfthatisprettycoolformanyreasons.Butwhatmakesitkiller
isthatthequerylanguagecomposes(actuallyalotofthingsmakeitkillerbutletsgowiththisfornow).
ThisiswhatletsRelaybuildupthequerypiecemealonacomponentbycomponentbasis.Ihighly
recommendwatchingthevideoofthesessionIcantdothisthingjusticeinaparagraph!
Mostofmyworklatelyhasbeenonjavascriptclientsideapplications.Imafanofisomorphicjavascript
butitsalotofwork.TheprojectImonisrenderedserverside(Play/scalatemplates)withBackbone/
Handlebarstakingoverontheclient.WerereallylookingatwayswecanimprovethingssoGraphQL
immediatelycaughtmyeye.ThepathIhavebeenadvocatingisaddingsomekindofAPIGatewaypattern.
Essentiallywehaveallthesedifferentresourcesmovies,series,episodes,networks,etcandwewantto
bundlethemupinapagespecificpayloadmoviedetailspage,networkslistingpage,etc.Sowedendup
withanendpointperpageperdevicetablet,phone,desktopanditwouldbetheprogrammersjobto
makesurethatendpointiskeptinsynceverytimetheclientisupdated.
Soclearlytherearesomedownsidesbutitisdoable.Netflixhastalkedaboutdoingsimilarthingsherewith
device/pagespecificendpoints.Theyvedescribeditasmakingpartoftheclientrunontheserver.
ButGraphQLisessentiallytheoneAPIGatewaytorulethemall.AndthenyouaddRelayontopofitto
builduptheexactqueryyouwantohmy.
SobasicallythisstuffissocoolIcantwaitforitsoImgoingtomakeacrappyimplementationmyself.
Roots
ImgoingtowalkthroughmyfirstexpirmentingwithGraphQL.AtthispointtheresourcesIknowaboutare
thesessionvideo(linkedabove)andtheUnofficialRelayFAQbyGregHurrell.
Atthetoplevelofthegraph/query(notsurewhattocallitexactlyletsgowithgraph)youhaveabunch
ofrootcalls.Wecanrepresentthisasamapwitheachrootcallasakeyandthenthefieldsofthatrootcallas
thevalueofthekey.Theshapeoftherootcallcanbewhateveryouwantaslongasituniquelyspecifiesthe
datayouwant.TheexamplesweveseensofarareNode(id)andViewer.
{
}
Node(1) { name }
Viewer { birthday { month } }
OnethingthatIdidntreallygraspwatchingthevideobuttheFAQpointedoutisthatNodeandViewerare
notsomemagicalGraphQLthingtheyarejustimplementationsforFacebooksneedsNodehastodowith
theirwholeFacebookGraphstuffandViewerisjustshorthandforthecurrentuser.Thismeansyoucan
createwhateverrootcallsyouneedforyourapplication.Wantthemodelintherootcall?Makea
Movie(12345)rootcall.HaveabunchofREST/HTTPservices?MakeaResource(/api/movie/12345)root
call.Wanttomixgithubintoyourapplication?AddaGithub(/whatever/github/looks/like)rootcall.
OfcourseImjustguessing.
Asfortherepresentationofthis,ImgoingtouseaVariant(akafancytuple?).AsItypethisIamovercome
withfearthatImusingthiswordwrong.ButafterwatchingJeanineAdkissonstalkatClojureConjIwant
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
1/6
7/19/2015
todescribeeverythingasaVariant.SoforNode(12345)wedhave[:node 12345]andVieweris[:viewer]
andResource(/api/movie/1234)is[:resource "/api/movie/1234"].Thetuplecanhaveasmanyfields
asitneeds.
{
Onelimitofthisisthatourlanguageneedstosupportvectorsasmapkeysandhavemeaningfulequalityso
thatifwejointwographsbothwith[:node 12345]wedontendupwithtwokeys.ClojureiswhatIm
usingandIthinkimmutablejspairedwithtransitjswillworkontheclientside(Iwantthistobetrue).
Fields
Sonowthatwehaverootcalls,whataboutthefields?Imgoingwithasetthathasfieldsaskeywordsor
tuplesfornestedfields.
{
[:node 12345] #{
:name
:profile_pic
:is_verified
[:birthday #{
:month
:year
}]
}
Representingnestedfieldsasatuplemightendupbeingaterribleideawhoknows!Ivereadhorrorstories
fromClojurepeopleaboutrepresentingtreesasvectorsinsteadofmapsbutcomeon,thatlooksso
convenient/succinct.
Theonethingmissingishowtohandleonetomany.ShortanswerIdontreallyknow.
[update:DanielSchaferhasclarifiedonetomanysintheFAQsoIupdatedthissection]
LonganswertheFAQhasbeenupdatedwithabitmoreinformationononetomanys.Itsoundslikethere
isanintermediateobjectbetweenthefieldandthechildnodesreferredtoastheconnectionobject.This
makessenseyouwantsomemetainformationaroundthecollection.Aprimaryusecaseispaginationas
discussedinthesession.Intermsofrepresentationwecankeepthesamestyle:
{
[:node 12345] #{
:name
[:friends {:first 1} #{
:count
[:edges #{
:cursor
[:node #{
:name
}]
}
}
}
Soyah,thatisugly,butcanberepresented.Andifweleaveedgesoffwecangetthecountignoringanycost
associatedwithfetchingthefullcollection.(andcountsoundslikeitisstilltrickythoughwhatisacountin
theworldofpagination?)
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
2/6
7/19/2015
Implementation
OvertheweekendImadeareallyroughimplementationoftheQuerypartofGraphQL(akatheeasy1%of
allitsfeatures)beforeIreadtheUnofficialFAQ.ThentheFAQmademehatemyimplementationandIm
startingoverandignoringonetomanysfornow.
SoformysecondtakeatthisImgoingtomaketherootcallsdispatchoffofthefirstfieldinthevariant(aka
fancytuple).ImusingamultimethodbecauseIliketheideaofbeingabletoextendtherootcallsfrom
anywhereincode.
(defmulti execute (fn [root fields context] (first root)))
Theexecutemultimethodwillprobablyhavetobecomeaprotocolatsomepointtoenablevalidationand
schemadefinitionsbutwhatever.
AndletssayyouwanttoimplementtheGithubrootcall!
(defmethod execute :github [[_ resource] fields context]
;; making a web request to github
)
Andnowwecanhydrateagraphwhichincludesbothdatomicandgithubrootcalls.
(hydrate {[:entity 12345] #{:name :birthday}
[:github "/some/endpoint"] #{:latest-commit}}
{:db :database})
Forbonuspointswecanmakeexecutorsreturncore.asyncchannelsandselectonthem,butIgottoleave
somethingforthenextblogpost.
Also,contexthereisbasicallytherequestcontextifthatmakessense.Auserhitsanendpoint,youget
yoursession,cookie,andproperparameters,etc,bundlethemupandthatisthecontext.Inthiscaseweneed
ourdatomicdatabaseandthecallerisresponsibleformakingsureitisinthecontext.Thisisabitopaquebut
worksfornow.
Nowweneedtoexecuteoneachfield.Thisisgoingtoberootcallspecific.
FordatomicIendedupusinganothermultimethod.ThereasonIwentwithamultimethodistosupport
computedfields.Forexampleyourdatabasemayonlyhavefirst-nameandlast-namebutyouwantto
supportnameatadataschemalevel.
(defmulti mapper (fn [field entity context] field))
(defmethod mapper :user/name [_ entity _]
(str (:user/first-name entity) " " (:user/last-name entity)))
(defmethod mapper :default [field entity _]
(get entity field))
(mapper :user/name
{:user/first-name "Erik" :user/last-name "Petersen" :user/is-verified true}
{}) ;; => "Erik Petersen"
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
3/6
7/19/2015
(mapper :user/is-verified
{:user/first-name "Erik" :user/last-name "Petersen" :user/is-verified true}
{}) ;; => true
Asyoucanseeinthecodeexampleweleaveadefaultimplementationthatsimplygetsattheentityitselfso
youonlyneedspecificoverridesonafieldbyfieldbasis.Onenicethingaboutdatomicisthatallfields
(attributes)arenamespacedsothismultimethodcanbeuniversal.
Inordertoexpandanentitywejustrunmapperoneachfield.Thisdoesnothandlemanytooneoroneto
many.Again,Imgoingtoskiponetomanyfornowbutwithmanytoonewecanjustuserecursion.
(defn expand-entity [entity fields context]
(into {}
(map (fn [field]
(cond (vector? field) (let [[key fields] field
reference (mapper key entity context)]
[key (expand-entity reference fields context)])
:else [field (mapper field entity context)]))
fields)))
Imnotsurehowbulletproofthisis,butwithsimplestuffitworks.core.matchwouldbeprettyswank
insteadofthatvector?call.
(expand-entity {:user/first-name "Erik"
:user/last-name "Petersen"
:user/is-verified true
:user/birthday {:birthday/day 13
:birthday/month 8
:birthday/year 1980}}
#{:user/name [:user/birthday #{:birthday/month :birthday/year}]}
{})
Wecanevenmakefakefields.
(defmethod mapper :user/best-friend [_ _ _]
{:user/first-name "Carly Rae"
:user/last-name "Jepsen"})
(expand-entity {:user/first-name "Pete"
:user/last-name "Hunt"}
#{[:user/best-friend #{:user/name}]}
{})
Sothatisprettyneato.
Onelastexampleissomethinglikemutual_friends.Imgoingtoignorethefactthatitisonetomanyand
justfocusonthefactthatthisisacomputedfieldthatdependsoncontext.Yourdatabaseisntgoingtostore
themutualfriendsitisgoingtostoreeachusersfriendsandthendoanintersectionbetweenthem.This
alsomeansyouneedtwousersasingleentityisntenoughtodescribemutual_friends.
Wecanusethecontexthere.Theassumptionisthatsomeplaceinyourmiddlewarethecurrentuserhasbeen
shovedintothecontext.Soifwewantamutual_friendscomputedfieldwewoulddo:
(defmethod mapper :user/mutual_friends [_ entity context]
(let [current-user (:user context)]
(set/intersection (:friends entity) (:friends current-user))))
Again,kindofsucksthatthiscontextisopaque.SomethingalongthelinesofPrismaticsplumbingwouldbe
coolsoyoucouldseetheschemaofrequiredinputsbutitsfinefornow.
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
4/6
7/19/2015
NowWhat?
Thispostislike20xlongerthanitshouldbe,butwhatever,allthisstuffissofunIcouldnthelpbutstream
ofconsciousnessaboutit.
Thenextstepsformearetogetthissecondgoofanimplementationworkingandhookingituptojavascript
andhopingIcanrepresentthegraphlikethisontheclientside(fingerscrossed).Thecurrentimplementation
IdidbubblesthebodyofthegraphupjustfineinJavascriptbutthewayIwasrepresentingrootcallswas
totallywrong.
var query = function (component, key) {
var params = component.queryParams
return component.queries[key](params);
};
var FriendInfo = React.createClass({
statics: {
queries: {
user: function () {
return Immutable.Set.of(
"user/name",
Immutable.List.of("user/mutual-friends", Immutable.Set.of("count"))
);
}
}
}
});
var FriendListItem = React.createClass({
statics: {
queries: {
user: function () {
return Immutable.Set.of("user/is-verified")
.union(query(ProfilePic, "user"))
.union(query(FriendInfo, "user"))
}
}
}
});
OfcourseonetomanyisimportantandImcurrentlyunsurehowtohandlethatbuthaveabetterideadueto
theFAQfeedback(thanks!).Idalsoliketomakeaweirdnondatomicrootcall,kindofliketheGithub
exampleIthrewaroundearlier.
FinallyIdliketogetvalidationandschemaworking.Datomichasagreatschemathatisjustdata.Thetype
andcardinalityarerightthereatyourfingertips!
IreallycantagreewiththispostmoreFacebookiskillingitrightnowandImsogladtheyaresharing.
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
5/6
7/19/2015
6Comments
HueyPetersen
Recommend 4
Share
Login
SortbyBest
Jointhediscussion
ValWaeselynck 6daysago
Ifyou'rewonderingaboutthebestdatarepresentationforqueries(e.gnestedfields)youmay
wanttotakeinspirationfromDatomic'sPullAPI:http://docs.datomic.com/pull.h...
Reply Share
JoeR.Smith amonthago
Goodstuff,thanksforwritingthisup!
Reply Share
NikolausGraf 4monthsago
Thanksforsharing:)
Reply Share
NickSchrock 6monthsago
Alsore:yourquestiononsemanticsofcount,itisthecountofthetotalnumberofedgesina
particularconnection(e.g.yourtotalfriendcountifthequeryis".friends").Thiswasabitpoorly
namedsinceitwasambiguous.
Reply Share
NickSchrock 6monthsago
HueywhereistheFAQyouarereferringto?
Reply Share
hueypetersen
Mod >NickSchrock
6monthsago
https://gist.github.com/wincen...
IlinkeditthefirsttimeImentionit...butgotlazyfortherestp
Reply Share
WHAT'STHIS?
ALSOONHUEYPETERSEN
Dijkstra'sAlgorithmasaSequence
5comments2yearsago
BuildingaTypeaheadDirectivewith
AngularJS
18comments2yearsago
ModelingQueriesinaGraphQLLikeWay
TheStateMachinesofcore.async
1comment5monthsago
7comments2yearsago
Privacy
Subscribe
d AddDisqustoyoursite
Youcanreachmeateyston@gmail.comorviatwitter@hueypetersen.
http://hueypetersen.com/posts/2015/02/02/first-thoughts-on-graph-ql/
6/6