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

GraphQL Highway

cockscomb
December 17, 2021

GraphQL Highway

銀座Rails#40

cockscomb

December 17, 2021
Tweet

More Decks by cockscomb

Other Decks in Technology

Transcript

  1. (SBQI2-Ͱ͸ΦʔόʔϑΣονϯάΛ๷͛Δ ඞཁҎ্ͷσʔλΛऔಘͯ͠͠·͏͜ͱΛΦʔόʔϑΣονϯάͱ͍͏ { "posts": [ { "title": "Hello World", "body":

    "Lorem ipsum", "authorId": "1" }, { "title": "Hi", "body": "Hi there.", "authorId": "2" } ] } { "posts": [ { "title": "Hello World" }, { "title": "Hi" } ] }
  2. (SBQI2-͸ΫϥΠΞϯτͷมԽʹॊೈ "1*ʹྺ࢙తܦҢ͕஝ੵ͍ͯ͘͜͠ͱΛ๷͛Δ { "title": "Hello World", "body": "Lorem ipsum", "tags":

    ["essay", "blog"], // ࠓ͸࢖͍ͬͯͳ͍͕ޓ׵ੑͷͨΊʹ࢒͢ "tag_objects": [ { "name": "essay", "id": "1" }, { "name": "blog", "id": "2" } ] }
  3. ΦϖϨʔγϣϯͷॻࣜ লུͰ͖Δ෦෼΋͋Δ query { post(id: "1") { title } }

    
 query GetPost { post(id: "1") { title } } 
 query GetPost($id: ID!) { post(id: $id) { title } }
  4. ΦϖϨʔγϣϯͷॻࣜ w RVFSZNVUBUJPOTVCTDSJQUJPO w ΦϖϨʔγϣϯ໊ w ม਺ w ϑΟʔϧυͷબ୒ w

    ύϥϝʔλΛऔΕΔ query GetPost($id: ID!) { post(id: $id) { title } }
  5. (SBQI2-ͷεΩʔϚ 4%- 4DIFNB%F fi OJUJPO-BOHVBHF type Query { # ...

    } type Mutation { # ... } schema { query: Query mutation: Mutation }
  6. (SBQI2-ͷܕ w εΧϥܕ w /PO/VMMܕ w Ϧετܕ w ྻڍܕ 


    w ΦϒδΣΫτܕ w Πϯϓοτܕ w ϢχΦϯܕ w ΠϯλʔϑΣʔε
  7. ྻڍܕ enum Order { ASC DESC } type Query {

    posts(order: Order = DESC): [Post!]! }
  8. ΦϒδΣΫτܕ type Author { name: String! } type Post {

    title: String! author: Author! } type Query { posts: [Post!]! }
  9. ϢχΦϯܕ type Comment { text: String! } type Trackback {

    title: String! url: String! } union Feedback = Comment | Trackback type Post { title: String! feedbacks: [Feedback!]! }
  10. ΠϯλʔϑΣʔε interface Publication { title: String! body: String! } type

    Page implements Publication { title: String! body: String! } type Post implements Publication { title: String! body: String! comments: [Comment!]! }
  11. σΟϨΫςΟϒ w εΩʔϚ΍ΦϖϨʔγϣϯʹϝλσʔλΛ͚ͭΒΕΔ directive @deprecated( reason: String = "No longer

    supported” ) on FIELD_DEFINITION | ENUM_VALUE type Query { posts: [Post!]! @deprecated }
  12. Τϥʔ͸ϨεϙϯεͷFSSPSTͰฦ͢ ΤϥʔͷϑΥʔϚοτ͸࢓༷ͰఆΊΒΕ͍ͯΔ { "data": null, "errors": [ { "message": "body

    could not be fetched", "locations": [ { "line": 4, "column": 4 } ], "path": ["post", "body"] } ] }
  13. Ϧιʔεͷൃݟ w Ϧιʔεͷൃݟ͕ελʔτ஍఺ w 3&45ͱಉ͡ w Ϧιʔε͸ΦϒδΣΫτܕͰදݱ w αʔόʔͷ಺෦࣮૷ͱ෼͚ͯߟ͑Δ w

    υϝΠϯϞσϧʹै͏ type Blog { id: ID! name: String! } type Post { id: ID! title: String! body: String! }
  14. άϥϑߏ଄Λ࡞Δ ؔ࿈͢ΔϦιʔεΛϑΟʔϧυͰḷΕΔΑ͏ʹ͢Δ type Author { id: ID! name: String! }

    type Post { id: ID! title: String! authorId: ID! } type Author { id: ID! name: String! } type Post { id: ID! title: String! author: Author! }
  15. άϥϑߏ଄Λ࡞Δ ؔ࿈͢ΔϦιʔεΛϑΟʔϧυͰḷΕΔΑ͏ʹ͢Δ type Query { posts( blogId: ID!, page: Int!

    ): [Post!]! } type Blog { posts(page: Int!): [Post!]! } type Query { blog(id: ID!): Blog! }
  16. ࠶औಘ͕ͳͥॏཁͳͷ͔ ϖʔδωʔγϣϯ w ϖʔδ໨Ҏ߱Λऔಘ͢Δͱ͖ type Blog { id: ID! posts(page:

    Int!): [Post!]! } query { blog(id: "1") { id posts(page: 1) { id } } }
  17. /PEFΠϯλʔϑΣʔε ࠶औಘՄೳͳϦιʔεΛҰൠԽͨ͠΋ͷ w 3FMBZ༝དྷ w ύʔϚϦϯΫΛ࣋ͭΑ͏ͳϦιʔε w JEͷ஋͸యܕతʹ͸ w CBTF

    ܕ໊ϓϥΠϚϦΩʔ w αʔόʔͰ͸ܕ໊ͰॲཧΛ෼͚Δ w ΫϥΠΞϯτ͸PQBRVFʹѻ͏ interface Node { id: ID! } type Post implements Node { id: ID! }
  18. $POOFDUJPO ϖʔδωʔγϣϯͷந৅Խ w 3FMBZ༝དྷ w ΧʔιϧΛ࢖ͬͨϖʔδωʔγϣϯ w ΫϥΠΞϯτϥΠϒϥϦʹΑͬͯ͸ w ࣗಈతʹϖʔδωʔγϣϯͰ͖Δ

    type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } type PostEdge { cursor: String! node: Post! } type PostConnection { pageInfo: PageInfo! edges: [PostEdge!]! } type Blog { posts(first: Int!, after: String): PostConnection! }
  19. 3FMBZͷ(SBQI2-4FSWFS4QFDJGJDBUJPO $POWFOUJPOPWFS$PO fi HVSBUJPO w 3FMBZ͸'BDFCPPL੡ͷ(SBQI2-ΫϥΠΞϯτϥΠϒϥϦ w αʔόʔʹ௥Ճͷ࢓༷Λཁٻ͢Δ w ΦϒδΣΫτͷ࠶औಘŠ/PEF

    w ϖʔδωʔγϣϯŠ$POOFDUJPO w 3FMBZΛ࢖Θͳ͍ͳΒै͏ඞཁ͸ͳ͍͕ɺ฿͓͍ͬͯͯଛ͸ͳ͍ w ͍ͣΕ΋Ұൠతͳ՝୊ͳͷͰ
  20. .VUBUJPO͸Ϧιʔεͷ$6%͔Β͸͡ΊΔ w DSFBUF3FTPVSDF w VQEBUF3FTPVSDF w EFMFUF3FTPVSDF w ҙຯ͝ͱʹ෼ׂͯ͠΋͍͍ w

    େ͖͘ͳΓ͗͢ͳ͍Α͏ʹ type Mutation { createPost( input: PostInput!): Post! updatePost( id: ID!, input: PostInput!): Post! deletePost(id: ID!): ID! createDraftPost( input: PostInput!): Post! publishPost(id: ID!): Post! }
  21. .VUBUJPOͷύϥϝʔλͱ໭Γ஋ w ύϥϝʔλ͸ΠϯϓοτܕΛ࢖͏ w ࣅͨߏ଄ʹͳΔͳΒڞ༗͢Δ w ฦΓ஋͸/PEF͔*%Λ࢖͏ w DSFBUFͱVQEBUFͰ͸/PEFΛฦ͢ w

    EFMFUFͰ͸*%Λฦ͢ input PostInput { title: String! body: String! } type Mutation { createPost( input: PostInput!): Post! updatePost( id: ID!, input: PostInput!): Post! deletePost(id: ID!): ID! }
  22. (SBQI2-ͷεΩʔϚઃܭ w ઌਓͷ஌ܙʹֶͿ w ఆੴʹै͏ w (JU)VCͷ(SBQI2-"1*ͳͲΛࢀߟʹ͢Δ w IUUQTHJUIVCDPN4IPQJGZHSBQIRMEFTJHOUVUPSJBM w

    ࠷ऴతʹ͸໨తΛୡ੒Ͱ͖Δ͔͕Ұ൪ॏཁ w ΫϥΠΞϯτʹͱͬͯ౎߹ͷ͍͍"1*͕͍͍"1*
  23. ίʔυϑΝʔετͰεΩʔϚΛग़ྗ͢Δ w εΩʔϚΛμϯϓ͢Δ w εΩʔϚ͸ϦϙδτϦʹೖΕΔ w εΩʔϚͷมߋ͸ϨϏϡʔ͢Δ w ࠷৽ͷεΩʔϚ͕ग़ྗ͞Ε͍ͯΔ͔͸ςετͰ୲อ w

    IUUQTHSBQIRMSVCZPSHUFTUJOHTDIFNB@TUSVDUVSFIUNM # lib/tasks/graphql.rake task dump_schema: :environment do schema_defn = GraphqlRubyExerciseSchema.to_definition schema_path = "app/graphql/schema.graphql" File.write(Rails.root.join(schema_path), schema_defn) puts "Updated #{ schema_path}" end
  24. ͻͱ·ͣΦϒδΣΫτܕΛ࡞Δ w ΦϒδΣΫτͱϑΟʔϧυ͕ॏཁ w %#্ͷߏ଄ʹ߹ΘͤΔඞཁ͸ͳ͍ w HSBQIRMSVCZͰ͸ $ rails g

    graphql:object Model w "DUJWF3FDPSEͷϞσϧͷ৘ใ͕ࣗಈతʹ࢖ΘΕΔ w ϛεϦʔσΟϯάͰ͸͋Δ module Types class UserType < Types :: BaseObject field :name, String, null: false end end
  25. جຊతʹ͸ϦκϧόΛॻ͚͹͍͍ Ϧκϧό˺ίϯτϩʔϥ w ϑΟʔϧυΛղܾ͢Δͷ͕Ϧκϧό w (SBQI2-ʹಛ༗ͷॲཧΛॻ͘ w HSBQIRMSVCZͰ͸ w ϑΟʔϧυͱಉ໊ͷϝιου

    module Types class QueryType < Types :: BaseObject include GraphQL :: Types :: Relay : : HasNodeField include GraphQL :: Types :: Relay : : HasNodesField field :all_blogs, BlogType.connection_type def all_blogs Blog.all.order(id: :desc) end end end
  26. Ϧκϧό͸ωετ͢Δ w ϦκϧόͰಘΒΕͨΦϒδΣΫτ͕ 
 ·ͨϦκϧόΛ࣋ͭ w 2VFSZˠ#MPHˠ1PTU w ศར w

    / ໰୊ͷԹচʜʜ module Types class BlogType < Types :: BaseObject implements GraphQL :: Types : : Relay :: Node global_id_field :id field :owner, UserType, null: false def owner object.owner end field :name, String, null: false field :posts, PostType.connection_type do end def posts object.posts.order(datetime: :desc) end end end
  27. ΠϯλʔϑΣʔε΍ϢχΦϯΛฦ͢ͱ͖͸஫ҙ ΦϒδΣΫτͱܕͷରԠΛࣔ͢ඞཁ͕͋Δ w ΠϯλʔϑΣʔεͱͯ͠ฦ͢ͱ͖ 
 ࣮ࡍʹ͸Ͳͷܕ͔ϥΠϒϥϦʹ఻͑Δ w HSBQIRMSVCZͰ͸ w Schema.resolve_type

    def self.resolve_type(abstract_type, obj, ctx) case obj when User Types :: UserType when Blog Types :: BlogType when Post Types :: PostType else raise(GraphQL :: RequiredImplementationMissingError) end end
  28. 3FMBZʹରԠͤ͞Δ HSBQIRMSVCZ͕͍͍ͩͨ΍ͬͯ͘ΕΔ $ rails generate \ graphql:install - - relay

    w (MPCBM*%Λར༻ͨ͠/PEFͷ࣮૷ def self.id_from_object(object, type_definition, query_ctx) object_id = object.to_global_id.to_s object_id = object_id.sub("gid: //#{ GlobalID.app}/", "") encoded_id = Base64.urlsafe_encode64(object_id) encoded_id = encoded_id.sub( /= +/, "") type_hint = type_definition.graphql_name.first " #{ type_hint}_ # { encoded_id}" end def self.object_from_id(encoded_id_with_hint, query_ctx) _type_hint, encoded_id = encoded_id_with_hint.split("_", 2) id = Base64.urlsafe_decode64(encoded_id) full_global_id = "gid: //#{ GlobalID.app}/ #{ id}" GlobalID :: Locator.locate(full_global_id) end
  29. 3FMBZʹରԠͤ͞Δ /PEFΠϯλʔϑΣʔε • GraphQL :: Types :: Relay : :

    Node w HMPCBM@JE@ fi FMEΛࢦఆ module Types class UserType < Types :: BaseObject implements GraphQL :: Types :: Relay :: Node global_id_field :id field :name, String, null: false end end
  30. 3FMBZʹରԠͤ͞Δ $POOFDUJPO w connection_type w ಺෦తʹP ff TFUϕʔεʹͳ͍ͬͯΔ module Types

    class QueryType < Types :: BaseObject field :all_blogs, BlogType.connection_type def all_blogs Blog.all.order(id: :desc) end end end
  31. ίϯςΩετΛ࢖͏ ϦΫΤετʹඥ͍ͮͨσʔλ͸ίϯςΩετʹ֨ೲ͢Δ w ϩάΠϯϢʔβʔͳͲ w ΫΤϦΛ࣮ߦ͢ΔίϯτϩʔϥʔͰ 
 ίϯςΩετΛ࡞Δ def execute

    variables = prepare_variables(params[:variables]) query = params[:query] operation_name = params[:operationName] context = { # Query context goes here, for example: # current_user: current_user, } result = GraphqlRubyExerciseSchema.execute( query, variables: variables, context: context, operation_name: operation_name ) render json: result rescue StandardError = > e raise e unless Rails.env.development? handle_error_in_development(e) end
  32. / ໰୊ w BVUIPSͷϦκϧόΛφΠʔϒʹ࣮૷ w 1PTUͷ਺͚ͩ4&-&$5 SELECT * FROM author

    WHERE id = ?; type Author { id: ID! name: String! } type Post { id: ID! title: String! author: Author! } type Query { allPosts(first: Int!, after: String): PostConnection! }
  33. / ໰୊ରࡦ σʔλϩʔμʔΛ࢖͏ w ಡΈࠐΈΛ஗Ԇͤ͞Δ࢓૊Έ SELECT * FROM author WHERE

    id IN (?, ?, ?); w HSBQIRMSVCZͰ͸ w GraphQL :: DataloaderΛ࢖͏ class Sources : : ActiveRecordObject < GraphQL : : Dataloader :: Source def initialize(model_class) @model_class = model_class end def fetch(ids) records = @model_class.where(id: ids) ids.map { |id| records.find { |r| r.id = = id.to_i } } end end field :author, UserType, null: false def author dataloader .with(Sources :: ActiveRecordObject, :: User) .load(object.author_id) end
  34. %P4߈ܸ w ෳࡶͳΫΤϦͰ%P4߈ܸͰ͖Δ w ಛʹ࠶ؼతͳεΩʔϚͰ͸༰қ w #MPHˠ1PTUˠ#MPHˠ1PTUˠʜ query { blog(id:

    "1") { posts(first: 100) { edges { node { blog { posts(first: 100) { edges { node { . . . } } } } } } } } }
  35. %P4߈ܸରࡦ ΫΤϦ౰ͨΓͷॲཧྔΛ੍໿͢Δ w λΠϜΞ΢τͤ͞Δʢuse GraphQL : : Schema : :

    Timeout, max_seconds: 2ʣ w ΫΤϦͷ"45ͷ࣌఺Ͱ੍໿ w ਂ੍͞ݶʢmax_depth 10ʣ w ෳࡶ͞Ͱ੍ݶʢmax_complexity 100ʣ w Ϩʔτ੍ݶ w (JU)VC͸࣌ؒ౰ͨΓͷෳࡶ͞ͷείΞͰ੍ݶ͍ͯ͠Δ