Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Power to the (Mobile) People! - GraphQL

Power to the (Mobile) People! - GraphQL

Introduction to GraphQL and the Clojure GraphQL implementation that Walmart is shortly to open-source.

As presented at https://www.meetup.com/clojure-pdx/events/236562367/

Howard M. Lewis Ship

January 05, 2017
Tweet

More Decks by Howard M. Lewis Ship

Other Decks in Technology

Transcript

  1. Hey server! I need this customer's zip code! Ok, here's

    everything I know about the customer: • first_name • last_name • middle_initial • left_foot_size • right_foot_size • favorite_color • post_number • alpha_sort • zodiac_sign • pet_name • phone_number • ...
  2. So, uh, which of those is the zip code? I

    think it's post_code? Ok ... why? Uh, database reasons, maybe?
  3. Also, I need some order history for this screen. Oh,

    here's the requests you need to make. All this request time is really noticeable in the app. Customers complain. Sorry. I can write a new endpoint for you, but it will be a couple of months until I can deploy it.
  4. CLIENT DEV GRIPES • One-size-fits-all data from the server
 Big

    responses
 ➠ Slow! • Multiple requests 
 ➠ Really Slow! • Mapping server names to "sensible" names 
 ➠ Busy Work! • Documentation — rarely exists, never up to date 
 ➠ Wasted Effort!
  5. SERVER SIDE DEV GRIPES • Every client need different data!


    
 Have to provide greatest common denominator • How can I provide documentation? How can I keep it up to date? • Customer data is one endpoint. Order history is another. Sorry, that's REST. • I want to give everyone what they need, but I'm drowning in code, tests, and technical debt!
  6. I've got all this data available. Take just what you

    need! Thanks! I just need the customer's zip code and a bit of order history for this screen. GraphQL
  7. GRAPHQL • A schema that describes all the data •

    A query language that allows clients to request just what they need • Introspection allows clients to see what data is available • NOT an object relational mapper
 NOT a database adapter
 
 Just a contract. Where the data comes from doesn't matter A QUERY LANGUAGE FOR APIS
  8. A QUERY LANGUAGE FOR APIS GRAPHQL • Originated at Facebook

    • "Powering mobile apps since 2012" • Open Source Specification in 2015 • http://graphql.org
  9. Hey server! I need this customer's zip code! Postal code.

    Whatever.
 
 { customer (id: "4483838") { zip_code: post_code } } Ok, here is it:
 
 {"data": {"customer" :
 {"zip_code": "97204"}}}
  10. { customer (id: "4483838") { zip_code: post_code } } {

    "customer_id": "4483838", "post_code": "97204", ...} RESOLVED VALUE "97204" NESTED RESOLVED VALUE {"zip_code": "97204"} SELECTED DATA { customer (id: "4483838") { zip_code: post_code } } { customer (id: "4483838") { zip_code: post_code } }
  11. FIELDS • Named slot in an Object or an Interface

    • A field has a type: • A scalar type • An Object, Interface, or Union • Optionally: non-nil • A list of the above
  12. FIELDS ARE FUNCTIONS FIELD ARGUMENTS { customer (id: "4483838") {

    zip_code: post_code orders (sort: "LATEST", count: 5) { ordered_at total } } } field arguments
  13. OBJECTS • Objects are containers of fields • There is

    no inheritance BUT • Objects may implement Interfaces • Objects must contain fields corresponding to each Interface
  14. INTERFACES • Interfaces are collections of fields • AND that's

    it • No inheritance • No partial implementations • Field type may be an Interface
  15. Human Character Droid id ID name String episodes [Episode] friends

    [Character] id ID name String episodes [Episode] force_side ForceSide friends [Character] id ID name String episodes [Episode] power_source PowerSource friends [Character] Implements
  16. Human Character Droid id ID name String episodes [Episode] friends

    [Character] id ID name String episodes [Episode] force_side ForceSide friends [Human] id ID name String episodes [Episode] power_source PowerSource friends [Character] Implements
  17. Queries hero(episode) Character human(id) Human droid(id) [Episode] { human(id: "LUKE")

    { name 
 friends { name }
 force_side }} {"data": {"hero":
 {"name": "Luke Skywalker", "friends": [ {"name": "Leia Organa"}, {"name": "R2D2"},
 ...],
 "force_side": "LIGHT"}}}
  18. UNIONS • Unions are collections of Objects • The objects

    may or may not share any fields • Objects may be members of more than one Union
  19. SIMPLE QUERIES { hero(episode: NEWHOPE) { name } } {"data":

    { "hero": {"name": "Luke Skywalker"} } }
  20. MULTIPLE QUERIES { new_hope: hero(episode: NEWHOPE) { name } empire:

    hero(episode: EMPIRE) { name } } {"data": { "new_hope": {"name": "Luke Skywalker"}, "empire": {"name": "Boba Fett"} } }
  21. EXPLICIT QUERY query { new_hope: hero(episode: NEWHOPE) { name }

    empire: hero(episode: EMPIRE) { name } } {"data": { "new_hope": {"name": "Luke Skywalker"}, "empire": {"name": "Boba Fett"} } }
  22. MUTATIONS mutation { add_favorite(userId: 1234, episode: NEWHOPE) { stars }

    } {"data": { "add_favorite": {"stars": 34976124} } }
  23. APPLY SELECTIONS BASED ON TYPE FRAGMENTS { hero { name

    friends { name ... on Human { force_side } } } }
  24. IS IT READY YET? • Alpha code • Uses Clojure

    1.9 — clojure.spec • Torturous internal review process
 
 We work for a really big the biggest company • Really hard coming up with a meaningful, unique name • Coming soon to GitHub and Clojars!
  25. id ID name String ... ... publishers [Company] BoardGame id

    ID name String description String Company search(String) [BoardGame] game(ID) BoardGame Queries
  26. PLAN • Schema as an EDN file • Load, attach

    resolvers, compile • Get XML from BGG, convert to Clojure data • Expose it all using Pedestal • Code at: 
 
 https://github.com/hlship/boardgamegeek-graphql-proxy
  27. COMPANY OBJECT SCHEMA {:objects { :Company { :description " ..."

    :fields { :id {:type Id} :name {:type String} :description {:type String} } } ... }
  28. BOARDGAME OBJECT SCHEMA :BoardGame {:description " ..." :fields {:id {:type

    ID} :name {:type String} :publish_year {:type Int} ... :publishers {:type (list Company) :args {:limit {:type Int :description " ..."}} :description " ..." :resolve :resolve-game-publishers}}}
  29. QUERIES SCHEMA :queries { :search {:type (list BoardGame) :description "

    ..." :args {:term {:type String :description " ..."}} :resolve :resolve-search} :game {:type BoardGame :description " ..." :args {:id {:type ID :description " ..."}} :resolve :resolve-game}}
  30. IT KNOWS YOUR DATA WHAT'S A FIELD RESOLVER? (defn ^:private

    resolve-search [context args value] [(client/search (:term args)) nil]) resolved value error map(s) • Application-defined context map • Field arguments • Containing field's resolved value
  31. PUBLISHERS RESOLVER (defn ^:private resolve-game-publishers [_ args board-game] (let [{:keys

    [limit]} args publisher-ids (cond ->> (:publisher-ids board-game) limit (take limit))] [(client/publishers publisher-ids) nil]))
  32. BGG-GRAPHQL-PROXY.SCHEMA SCHEMA: ASSEMBLE & COMPILE (defn bgg-schema [] ( ->

    (io/resource "bgg-schema.edn") slurp edn/read-string (attach-resolvers {:resolve-game resolve-board-game :resolve-search resolve-search :resolve-game-publishers resolve-game-publishers}) schema/compile))
  33. RUNNING QUERIES AT REPL (execute schema "{search (term: \"tiny epic\")

    { name publish_year }}" nil nil) => {:data {:search [#ordered/map([:name "Tiny Epic Defenders"] [:publish_year 2015]) #ordered/map([:name "Tiny Epic Defenders: Kickstarter Mini-Expansion"] [:publish_year 2014]) #ordered/map([:name "Tiny Epic Galaxies"] [:publish_year 2015]) #ordered/map([:name "Tiny Epic Galaxies Deluxe Edition"] [:publish_year 2015]) #ordered/map([:name "Tiny Epic Galaxies: Beyond the Black"] [:publish_year 2017]) #ordered/map([:name "Tiny Epic Galaxies: Beyond The Black – Drones Mini Expansion"] [:publish_year 2017]) #ordered/map([:name "Tiny Epic Galaxies: Satellites & Super Weapons Mini Expansion"] [:publish_year 2015]) #ordered/map([:name "Tiny Epic Kingdoms"] [:publish_year 2014]) #ordered/map([:name "Tiny Epic Kingdoms 2nd Edition: Deluxe Promo Pack"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Kingdoms: Deluxe Promo Pack"] [:publish_year 2014]) #ordered/map([:name "Tiny Epic Kingdoms: Heroes' Call"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Kingdoms: Heroes' Call Deluxe Edition"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Kingdoms: Heroes' Call – Deluxe Promo Pack and Mini Expansion"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Quest"] [:publish_year 2017]) #ordered/map([:name "Tiny Epic Western"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Western: Kickstarter Deluxe Promo Pack"] [:publish_year 2016]) #ordered/map([:name "Tiny Epic Western: The Tycoon"] [:publish_year 2016])]}} application context variables
  34. USING PEDESTAL AS A WEB ENDPOINT (defn ^:private graphql-handler [compiled-schema]

    (fn [request] (let [vars (variable-map request) query (extract-query request) result (execute compiled-schema query vars nil) status (if ( -> result :errors seq) 400 200)] {:status status :headers {"Content-Type" "application/json"} :body (json/write-str result)}))) (defn ^:private routes [compiled-schema] (let [query-handler (graphql-handler compiled-schema)] (route/expand-routes #{["/" :get index-handler :route-name :graphiql-ide-index] ["/graphql" :post query-handler :route-name :graphql-post] ["/graphql" :get query-handler :route-name :graphql-get]})))