You are on page 1of 6

7/19/2015

First Thoughts on GraphQL

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

First Thoughts on GraphQL

todescribeeverythingasaVariant.SoforNode(12345)wedhave[:node 12345]andVieweris[:viewer]
andResource(/api/movie/1234)is[:resource "/api/movie/1234"].Thetuplecanhaveasmanyfields
asitneeds.
{

[:node 42] { name }


[:viewer] { birthday { month } }
[:movie 13] { title, rating }

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

First Thoughts on GraphQL

Implementation
OvertheweekendImadeareallyroughimplementationoftheQuerypartofGraphQL(akatheeasy1%of
allitsfeatures)beforeIreadtheUnofficialFAQ.ThentheFAQmademehatemyimplementationandIm
startingoverandignoringonetomanysfornow.
SoformysecondtakeatthisImgoingtomaketherootcallsdispatchoffofthefirstfieldinthevariant(aka
fancytuple).ImusingamultimethodbecauseIliketheideaofbeingabletoextendtherootcallsfrom
anywhereincode.
(defmulti execute (fn [root fields context] (first root)))

;; example datomic executor


(defmethod execute :entity [[_ id] fields {:keys [db] :as context}]
(let [entity (d/entity db id)]
;; expand the entity
))
(defn hydrate [graph context]
(into {}
(map (fn [[root fields]] [root (execute root fields context))]
graph)))

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

First Thoughts on GraphQL

(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}]}
{})

;; => {:user/name "Erik Petersen", :user/birthday {:birthday/year 1980, :birthday/month 8}}

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}]}
{})

;; => {:user/best-friend {:user/name "Carly Rae Jepsen"}}


;; oh, sorry if you're best friend isn't as cool as Pete Hunt's

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

First Thoughts on GraphQL

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

First Thoughts on GraphQL

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

You might also like