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

Что скрывает «под капотом» GraphQL Ruby, и как ...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Dmitry Tsepelev Dmitry Tsepelev
September 28, 2019

Что скрывает «под капотом» GraphQL Ruby, и как это помогает писать более элегантный API

Разобраться в большом open-source проекте не так то просто: большой объем исходного кода и обилие неочевидных решений, появившихся в процессе эволюции проекта повышают порог входа. В докладе я рассказажу о том, что происходит, когда объявляется новый тип, а также о том, как происходит обработка запроса на эндпоинт /graphql. Эти знания помогут нам понять, как работают плагины для graphql-ruby и даже написать свое собственное несложное расширение!
Доклад будет полезен:

тем, кто работает с graphql-ruby, и хочет знать, что там внутри
тем, кто хочет понять, как реализуется среда исполнения GraphQL
тем, кто хочет научиться разбираться с устройством популярных open-source библиотек
тем, кто хочет научиться контрибутить в open-source

Avatar for Dmitry Tsepelev

Dmitry Tsepelev

September 28, 2019
Tweet

More Decks by Dmitry Tsepelev

Other Decks in Programming

Transcript

  1. graphql-ruby under the hood and how to write more elegant

    APIs Dmitry Tsepelev, Evil Martians
  2. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 6 uses graphql-ruby and wants to

    know how it works internally wants to understand, how GraphQL execution engines are implemented wants to extend the API of graphql-ruby wants to learn, how to dig into a popular open-source project and start contributing THIS TALK IS FOR PEOPLE, WHO
  3. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 7 GraphQL query language how to

    use GraphQL in your projects pros and cons of GraphQL as a technology WHAT WE ARE NOT GOING TO DISCUSS
  4. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 8 DUMP 2019 Saint P Rubyconf

    2019 https:!//youtu.be/xUrLslKdnr8 https:!//youtu.be/CjOwKbf8L3I?t=9615
  5. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 11 every entity is a type

    each type has a list of fields some types are connected DATA AS A GRAPH
  6. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 12 Request: query { user(id: 42)

    { orders { items { product { title } quantity } } } } Response: { "data": { "user": { "orders": [ { "items": [ { "product": { "title": "iPhone 7" }, "quantity": 2 } ] } ] } } } QUERY LANGUAGE
  7. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 15 gem serializes the data you

    provide implements execution engine rules WHAT IS GRAPHQL-RUBY?
  8. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 16 $ rails generate graphql:install route

    post "/graphql", to: "graphql#execute" GraphqlController app/graphql/ contains some base classes
  9. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 17 CONTROLLER AND SCHEMA class GraphqlController

    < ApplicationController def execute result = GraphqlSchema.execute( params[:query], variables: params[:variables], context: {}, operation_name: params[:operationName] ) render json: result end end class GraphqlSchema < GraphQL!::Schema query Types!::QueryType end
  10. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 18 ROOT FIELDS class Types!::QueryType <

    GraphQL!::Schema!::Object field :user, Types!::UserType, null: true do argument :id, ID, required: true end def user(id:) User.find(id) end end
  11. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 19 TYPE DEFINITION class Types!::UserType <

    GraphQL!::Schema!::Object field :id, ID, null: false field :name, String, null: false field :orders, resolver: Resolvers!::OrderResolver end
  12. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 22 GraphQL::Schema class GraphQL!::Schema class !<<

    self def query(query_type) @query_type = query_type end end end class GraphqlSchema < GraphQL!::Schema query Types!::QueryType end application code library code
  13. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 23 class GraphqlSchema < GraphQL!::Schema lazy_resolve

    OrdersResolver, :orders end class Types!::UserType < GraphQL!::Schema!::Object field :orders, [OrderType], null: false def orders OrdersResolver.new(context, object.id) end end GraphQL::Schema#lazy_resolve
  14. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 24 class OrdersResolver def initialize(query_ctx, user_id)

    @user_id = user_id @lazy_state = query_ctx[:lazy_find_orders] !!||= { pending_ids: Set.new, loaded_ids: {} } @lazy_state[:pending_ids] !<< user_id end def orders loaded_records = @lazy_state[:loaded_ids][@user_id] return loaded_records if loaded_records pending_ids = @lazy_state[:pending_ids].to_a @lazy_state[:loaded_ids] = Order.where(user_id: pending_ids).group_by(&:user_id) @lazy_state[:pending_ids].clear @lazy_state[:loaded_ids][@user_id] end end GraphQL::Schema#lazy_resolve
  15. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev class GraphQL!::Schema class !<< self def

    lazy_resolve(lazy_class, value_method) lazy_classes[lazy_class] = value_method end def lazy_classes @lazy_classes !!||= {} end end end GraphQL::Schema#lazy_resolve 25
  16. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 26 GraphQL::Schema::Object class GraphQL!::Schema!::Object class !<<

    self def field(*args, !**kwargs, &block) own_fields[field_defn.name] = field_class.from_options( *args, !**kwargs, &block ) end def own_fields @own_fields !!||= {} end end end class Types!::UserType field :id, ID, null: false end application code library code
  17. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 27 GraphQL::Schema::Object class GraphQL!::Schema!::Object class !<<

    self def field(*args, !**kwargs, &block) own_fields[field_defn.name] = GraphQL!::Schema!::Field.from_options( *args, !**kwargs, &block ) end def own_fields @own_fields !!||= {} end end end class Types!::UserType field :id, ID, null: false end application code library code
  18. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field 28 class GraphQL!::Schema!::Field def self.from_options(name

    = nil, …, !**kwargs, &block) kwargs[:name] = name if name new(!**kwargs, &block) end attr_accessor :name def initialize(…, &definition_block) @name = name @type = type @null = null … end end
  19. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev &definition_block 29 class Types!::QueryType < GraphQL!::Schema!::Object

    field :user, Types!::UserType, null: true do argument :id, ID, required: true end end
  20. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 30 GraphQL::Schema::Field class GraphQL!::Schema!::Field def initialize(!!...,

    &definition_block) instance_eval(&definition_block) if definition_block end def argument(*args, !**kwargs, &block) arg_defn = GraphQL!::Schema!::Argument.new(*args, !**kwargs, &block) own_arguments[arg_defn.name] = arg_defn end def own_arguments @own_arguments !!||= {} end end
  21. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 31 #<GraphQL!::Schema!::Field @name="name", @resolver_class=nil, @resolver_method=:name >

    FIELD DATA STRUCTURE class Types!::UserType < GraphQL!::Schema!::Object field :id, ID, null: false field :name, String, null: false field :orders, resolver: Resolvers!::OrderResolver end Types!::UserType.fields
  22. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 32 #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver

    @own_arguments= { "page"!=> #<Argument @name="page", @null=true, @type_expr=Integer>, "perPage"!=> #<Argument @name="perPage", @null=true, @type_expr=Integer> } > class Types!::UserType < GraphQL!::Schema!::Object field :id, ID, null: false field :name, String, null: false field :orders, resolver: Resolvers!::OrderResolver end Types!::UserType.fields FIELD DATA STRUCTURE
  23. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema#execute class GraphQL!::Schema def self.execute(query_str =

    nil, !**kwargs) kwargs[:query] = query_str if query_str all_results = GraphQL!::Execution!::Multiplex.run_all(self, [kwargs]) all_results[0] end end 34
  24. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev class GraphQL!::Execution!::Multiplex class !<< self def

    run_all(schema, query_options, *args) queries = query_options.map do |opts| GraphQL!::Query.new(schema, nil, opts) end run_queries(schema, queries, *args) end end end 35 Schema.execute ⇨ Multiplex.run_all GraphQL::Execution::Multiplex.run_all
  25. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev @operation_type= @name= @name= GraphQL::Query#prepare_ast 37 query

    user name Document OperationDefinition Field Field @selections @selections @definitions
  26. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev class GraphQL!::Execution!::Multiplex class !<< self def

    run_all(schema, query_options, *args) queries = query_options.map do |opts| GraphQL!::Query.new(schema, nil, opts) end run_queries(schema, queries, *args) end end end GraphQL::Execution::Multiplex.run_all 38 Schema.execute ⇨ Multiplex.run_all
  27. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Execution::Multiplex#run_as_multiplex class GraphQL!::Execution!::Multiplex def run_as_multiplex(multiplex) strategy

    = multiplex.schema.query_execution_strategy results = multiplex.queries.map do |query| strategy.begin_query(query, multiplex) end results.each_with_index.map do |data_result, idx| multiplex.queries[idx].result end end end 39 Schema.execute Multiplex.run_all … ⇨ Multiplex#run_as_multiplex
  28. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Execution::Execute 40 class GraphQL!::Execution!::Execute def self.begin_query(query,

    _multiplex) ExecutionFunctions.resolve_root_selection(query) end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex ⇨ Execute.begin_query
  29. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev ExecutionFunctions.resolve_root_selection 41 module ExecutionFunctions def resolve_root_selection(query)

    operation = query.selected_operation root_type = query.root_type_for_operation( operation.operation_type ) resolve_selection( query.root_value, root_type, query.context ) end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ⇨ ExecutionFunctions.resolve_root_selection
  30. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev ExecutionFunctions.resolve_selection 42 module ExecutionFunctions def resolve_selection(object,

    current_type, current_ctx) current_ctx.value = {} selections_on_type = current_ctx.irep_node .typed_children[current_type] selections_on_type.each do |name| field_ctx = current_ctx.spawn_child(!!...) resolve_field(object, field_ctx) current_ctx.value[name] = field_ctx end current_ctx.value end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ⇨ ExecutionFunctions.resolve_selection resolve_selection
  31. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev module ExecutionFunctions def resolve_field(object, field_ctx) raw_value

    = field_ctx.schema .middleware .invoke(object, field_ctx) field_ctx.value = resolve_value( raw_value, field_ctx.type, field_ctx ) end end ExecutionFunctions.resolve_field 43 Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ExecutionFunctions.resolve_selection ⇨ ExecutionFunctions.resolve_field resolve_field
  32. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev FieldResolveStep 45 module FieldResolveStep def self.call(

    _parent_type, parent_object, field_definition, field_args, context ) field_definition.resolve( parent_object, field_args, context ) end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ExecutionFunctions.resolve_selection ExecutionFunctions.resolve_field … ⇨ FieldResolverStep.call
  33. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 46 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = if @resolver_class @resolver_class.new(object: e_obj, context: ctx) else e_obj end if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ExecutionFunctions.resolve_selection ExecutionFunctions.resolve_field … FieldResolverStep.call ⇨ Field#resolve resolve
  34. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 47 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = if @resolver_class @resolver_class.new(object: e_obj, context: ctx) else e_obj end if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver @resolver_method=:resolve >
  35. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 48 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = OrderResolver.new( object: e_obj, context: ctx ) if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver @resolver_method=:resolve >
  36. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 49 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = OrderResolver.new( object: e_obj, context: ctx ) if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver @resolver_method=:resolve >
  37. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 50 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = OrderResolver.new( object: e_obj, context: ctx ) field_receiver.public_send(@resolver_method, !**e_args) end end end resolve #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver @resolver_method=:resolve >
  38. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 51 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = OrderResolver.new( object: e_obj, context: ctx ) field_receiver.public_send(:resolve, !**e_args) end end end resolve #<GraphQL!::Schema!::Field @method_str="orders", @method_sym=:orders, @name="orders", @resolver_class=OrderResolver @resolver_method=:resolve >
  39. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 52 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = if @resolver_class @resolver_class.new(object: e_obj, context: ctx) else e_obj end if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="name", @method_sym=:name, @name="name", @resolver_class=nil, @resolver_method=:name >
  40. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 53 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = e_obj if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="name", @method_sym=:name, @name="name", @resolver_class=nil, @resolver_method=:name >
  41. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 54 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = e_obj if field_receiver.respond_to?(@resolver_method) field_receiver.public_send(@resolver_method, !**e_args) else resolve_field_method(field_receiver, e_args, ctx) end end end end resolve #<GraphQL!::Schema!::Field @method_str="name", @method_sym=:name, @name="name", @resolver_class=nil, @resolver_method=:name >
  42. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev GraphQL::Schema::Field#resolve 55 class GraphQL!::Schema!::Field def resolve(object,

    args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| field_receiver = e_obj field_receiver.public_send(:name, !**e_args) end end end resolve #<GraphQL!::Schema!::Field @method_str="name", @method_sym=:name, @name="name", @resolver_class=nil, @resolver_method=:name >
  43. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev module ExecutionFunctions def resolve_field(object, field_ctx) raw_value

    = field_ctx.schema .middleware .invoke(object, field_ctx) field_ctx.value = resolve_value( raw_value, field_ctx.type, field_ctx ) end end ExecutionFunctions.resolve_field 56 Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ExecutionFunctions.resolve_selection ⇨ ExecutionFunctions.resolve_field
  44. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev ExecutionFunctions.resolve_value 57 module ExecutionFunctions def resolve_value(value,

    field_type, field_ctx) field_defn = field_ctx.field case field_type.kind when GraphQL!::TypeKinds!::SCALAR field_type.coerce_result(value, field_ctx) when GraphQL!::TypeKinds!::OBJECT resolve_selection(value, field_type, field_ctx) end end end Schema.execute Multiplex.run_all … Multiplex#run_as_multiplex Execute.begin_query ExecutionFunctions.resolve_root_selection ExecutionFunctions.resolve_selection ExecutionFunctions.resolve_field … ⇨ ExecutionFunctions.resolve_value resolve_value
  45. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev class GraphQL!::Execution!::Multiplex def run_as_multiplex(multiplex) strategy =

    multiplex.schema.query_execution_strategy results = multiplex.queries.map do |query| strategy.begin_query(query, multiplex) end results.each_with_index.map do |data_result, idx| multiplex.queries[idx].result end end end GraphQL::Execution::Multiplex#run_as_multiplex 58 Schema.execute Multiplex.run_all … ⇨ Multiplex#run_as_multiplex
  46. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 60 GRAPHQL PARSER BENCHMARK fields –

    count of fields in each selection set nested levels – count of nested levels query { field1 { field1 field2 } field2 { field1 field2 } } fields = 2, nested levels = 1
  47. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 61 user system total real 1

    fields, 0 nested levels: 0.000153 0.000002 0.000155 ( 0.000152) 1 fields, 2 nested levels: 0.000188 0.000001 0.000189 ( 0.000187) 1 fields, 4 nested levels: 0.000252 0.000001 0.000253 ( 0.000240) 2 fields, 0 nested levels: 0.000134 0.000013 0.000147 ( 0.000132) 2 fields, 2 nested levels: 0.000420 0.000006 0.000426 ( 0.000411) 2 fields, 4 nested levels: 0.001695 0.000001 0.001696 ( 0.001694) 4 fields, 0 nested levels: 0.000154 0.000001 0.000155 ( 0.000153) 4 fields, 2 nested levels: 0.001744 0.000001 0.001745 ( 0.001744) 4 fields, 4 nested levels: 0.032188 0.000269 0.032457 ( 0.032581) 8 fields, 0 nested levels: 0.000316 0.000034 0.000350 ( 0.000352) 8 fields, 2 nested levels: 0.013464 0.000218 0.013682 ( 0.013917) 8 fields, 4 nested levels: 0.910159 0.008081 0.918240 ( 0.919507) GRAPHQL PARSER BENCHMARK
  48. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 62 30.50 kB (285 objects) /users/1?include=orders&fields[users]=id,name&fields[orders]=id,placed_at

    { "data" !=> { "id"!=>"1", "type"!=>"users", "links"!=>{"self"!=>"users/1"}, "attributes"!=>{"id"!=>"1", "name"!=>"John Doe"} }, "included"!=> [ { "id"!=>"1", "type"!=>"orders", "links"!=>{"self"!=>"orders/1"}, "attributes"!=>{"id"!=>1, "placed_at"!=>"2019-09-21T12:56:31+03:00"} }, { "id"!=>"2", "type"!=>"orders", "links"!=>{"self"!=>"orders/2"}, "attributes"!=>{"id"!=>2, "placed_at"!=>"2019-09-21T12:56:31+03:00"} } ] } MEMORY PROFILER (JSON API)
  49. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 63 113.19 kB (1059 objects) query

    GetUser($id: ID!) { user(id: $id) { id name orders { id placedAt } } } { "data" !=> { "user" !=> { "id"!=>"1", "name"!=>"John Doe", "orders"!=>[ { "id"!=>"1", "placedAt"!=>"2019-09-21T13:00:02+03:00" }, { "id"!=>"2", "placedAt"!=>"2019-09-21T13:00:02+03:00" } ] } } } MEMORY PROFILER (GRAPHQL)
  50. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 65 BLOCK-BASED API Types!::Post = GraphQL!::ObjectType.define

    do field :comments, types[Types!::Comments] do argument :orderBy, Types!::CommentOrder resolve !->(obj, args, ctx) { obj.comments.order(args[:orderBy]) } end end
  51. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 66 looks more like JavaScript than

    Ruby problems with constant loading (especially in Rails) hard to patch https:!//rmosolgo.github.io/blog/2018/03/25/why-a-new-schema-definition-api/ BLOCK-BASED API
  52. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 67 graphql-ruby supports both APIs some

    classes look more complex than they could some old plugins do not work BLOCK-BASED API
  53. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 69 class GraphqlSchema < GraphQL!::Schema query

    QueryType use GraphQL!::Batch end class QueryType < GraphQL!::Schema!::Object field :product_title, String, null: true do argument :id, ID, required: true end def product_title(id:) RecordLoader.for(Product).load(id).then(&:title) end end SETUP AND USAGE
  54. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 70 class RecordLoader < GraphQL!::Batch!::Loader def

    initialize(model) @model = model end def perform(ids) @model.where(id: ids).each do |record| fulfill(record.id, record) end ids.each do |id| fulfill(id, nil) unless fulfilled?(id) end end end LOADING DATA
  55. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev class GraphQL!::Schema class !<< self def

    use(plugin, options = {}) plugins !<< [plugin, options] end def plugins @plugins !!||= [] end def to_graphql plugins.each { |plugin| plugin.use(self) } end end end 71 HOW SCHEMA LOADS PLUGINS
  56. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 72 PATCHING SCHEMA WITH #use module

    GraphQL"::Batch def self.use(schema_defn, executor_class: GraphQL!::Batch!::Executor) instrumentation = GraphQL!::Batch!::SetupMultiplex.new( schema, executor_class: executor_class ) schema_defn.instrument(:multiplex, instrumentation) schema_defn.lazy_resolve(!::Promise, :sync) end end
  57. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 73 REDEFINING FIELD RESOLVER class GraphQL!::Batch!::SetupMultiplex

    def instrument(type, field) old_resolve_proc = field.resolve_proc field.redefine do resolve !->(obj, args, ctx) { !::Promise.sync(old_resolve_proc.call(obj, args, ctx)) } end end end
  58. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev HOW TO EXTEND A FIELD 75

    class Types!::QueryType < GraphQL!::Schema!::Object field :users, [Types!::UserType], null: false, extensions: [SearchableExtension] def users(query:) User.search(query) end end
  59. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev ADDING ARGUMENT TO A FIELD USING

    EXTENSION 76 class SearchableExtension < GraphQL!::Schema!::FieldExtension def apply field.argument(:query, String, required: false) end end
  60. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev HOW EXTENSIONS WORK 77 class GraphQL!::Schema!::Field

    def resolve(object, args, ctx) with_extensions(object, args, ctx) do |e_obj, e_args| # resolve logic end end def with_extensions(obj, args, ctx) extensions.each do |extension| obj, args = extension.resolve( object: obj, arguments: args, context: ctx ) end yield(obj, args) end end
  61. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev PLUGIN IN 5 MINUTES ⏰ 78

    https:!//gist.github.com/DmitryTsepelev/065bb6bc796898f5745c4209d1b4bb21
  62. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 79 WHAT WE ARE BUILDING class

    QueryType < BaseObject field :cached_val, String, null: false def cached_val "I'm cached at "#{Time.now}" end end
  63. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 80 ADDING EXTENSION TO THE FIELD

    class QueryType < BaseObject field :cached_val, String, null: false, extensions: [CacheExtension] def cached_val "I'm cached at "#{Time.now}" end end
  64. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 81 HOW TO IMPLEMENT THE EXTENSION

    class CacheExtension < GraphQL!::Schema!::FieldExtension def resolve(object:, arguments:, !**rest) key = cache_key(object, arguments) store[key] !!||= yield(object, arguments) end private def store Thread.current[:field_cache] !!||= {} end def cache_key(object, arguments) ""#{object.class.name}-"#{@field}-"#{arguments.hash}" end end
  65. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 82 I WANT TO USE KWARG!

    class QueryType < BaseObject field :cached_val, String, null: false, cached: true def cached_val "I'm cached at "#{Time.now}" end end
  66. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 83 PATCHING BASE CLASS class BaseObject

    < GraphQL!::Schema!::Object field_class.prepend(Module.new do def initialize(*args, !**kwargs, &block) if kwargs.delete(:cached) kwargs[:extensions] !!||= [] !<< CacheExtension end super end end) end
  67. RUBYRUSSIA 2019 DmitryTsepelev @dmitrytsepelev 84 graphql-ruby is a complex library

    with a complex architecture this architecture allows to extend the functionality of the library benefits coming with GraphQL do not require to sacrifice the performance TO SUM UP