GraphQL, eins og hvaða tækni sem er, hefur sín vandamál; sum þeirra stafa beint af arkitektúrnum en önnur eru nákvæmlega þau sömu og í hvaða annarri forritun sem er. Lausnirnar eru hins vegar gjörólíkar.
Hér er tómt.
Til að kynna vandamálið skulum við gera ráð fyrir eftirfarandi forritunararkitektúr:
Og hér er samsvarandi fyrirspurn í GraphQL til að hlaða niður gögn. Við sækjum allar tenglar, ásamt höfundinum og tenglum hans sem bætast við kerfið,
{
allLinks {
id
url
description
createdAt
postedBy {
id
name
links {
id
}
}
}
}
Eins og sýnt er hér að neðan sjáum við hið klassíska n + 1-vandamál með tengslum.
Link Load (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
↳ app/controllers/graphql_controller.rb:5:in `execute'
Notendahleðsla (0,3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 40], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.3ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ? [["user_id", 40]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Hleðsla notanda (0,1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 38], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.1ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ? [["user_id", 38]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Hleðsla notanda (0,2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.1ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ? [["user_id", 36]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Hleðsla notanda (0,1 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 34], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.2ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ? [["user_id", 34]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Hleðsla notanda (0,1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 32], ["LIMIT", 1]]
Í þessu tilfelli virkar það nákvæmlega eins og þetta stykki af kóði: Tengja.alla.korta(&:notandi).korta(&:tenglar).
Það virðist sem við vitum lausnina á vandamálinu: Link.includes(user: :links).map(&:user).map(&:links), en mun það virka í raun? Skoðum það!
Til að staðfesta lagfæringu breytti ég GraphQL Fyrirspurn til að nota aðeins nokkur reiti og engin tengsl.
{
allLinks {
id
url
description
createdAt
}
}
Því miður sýnir niðurstaðan að þrátt fyrir skort á tenglum varðandi notandann og tengla hans bætum við þessum gögnum við gagnagrunnsfyrirspurnina. Því miður eru þau óþörf og með enn flóknari uppbyggingu reynast þau einfaldlega óhagkvæm.
Í GraphQL, slík vandamál eru leyst á annan hátt, einfaldlega með því að hlaða gögnum í lotum, með þeirri forsendu að gögnin séu nauðsynleg þegar þau eru sett í fyrirspurnina. Þetta er svokallað letilegt hleðslufyrirkomulag. Eitt af vinsælustu bókasöfnunum er https://github.com/Shopify/graphql-batch/.
Því miður er uppsetningin ekki eins vandræðalaus og hún kann að virðast. Gagnalóðararnir eru fáanlegir hér: https://github.com/Shopify/graphql-batch/tree/master/examples, ég meina Upphleðsluforrit flokk ogFélagasamhlutlari flokkur. Skoðum að setja upp klassískt gem 'graphql-batch' bókasafn og bæta því síðan við skemað okkar, sem og hleðsluforritum:
# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
query Types::QueryType
mutation Types::MutationType
use GraphQL::Batch
...
end
Og tegundir okkar:
# graphql-ruby/app/graphql/types/link_type.rb
module Types
class LinkType < BaseNode
field :created_at, DateTimeType, null: false
field :url, String, null: false
field :description, String, null: false
field :posted_by, UserType, null: false, method: :user
field :votes, [Types::VoteType], null: false
def user
Loaders::RecordLoader.for(User).load(object.user_id)
end
end
end
# graphql-ruby/app/graphql/types/user_type.rb
module Types
class UserType < BaseNode
field :created_at, DateTimeType, null: false
field :name, String, null: false
field :email, String, null: false
field :votes, [VoteType], null: false
field :links, [LinkType], null: false
def links
Loaders::AssociationLoader.for(User, :links).load(object)
end
end
end
Sem afleiðing af notkun hleðslutólanna samræmum við gögnin og förum í tvær einfaldar SQL-fyrirspurnir til að sækja gögnin:
N + 1-fyrirspurnir eru ekki allt, í GraphQL Við getum frjálslega borið næstu eiginleika yfir. Sjálfgefið er gildi þeirra stillt á 1. Þetta getur stundum verið of mikið fyrir netþjóninn, sérstaklega í aðstæðum þar sem við getum frjálslega raðað gögnum í hólf. Hvernig eigum við að takast á við þetta? Við getum takmarkað flækjustig fyrirspurnarinnar, en til þess þurfum við einnig að tilgreina kostnað þeirra í eiginleikunum. Sjálfgefið er gildi þeirra stillt á 1. Við stillum þennan kostnað með því að nota flækjustig: eiginleiki, þar sem við getum slegið inn gögn: svið: tenglar, [Tengiltípa], null: falskur, flækjustig: 101. Ef takmörkun á að virka, þarftu enn að setja hámarksmörk í kerfið þitt:
class GraphqlTutorialSchema < GraphQL::Schema
query Types::QueryType
mutation Types::MutationType
use GraphQL::Batch
max_complexity 100
...
end
Eftirlit
GraphQL Vinnur fyrirspurnir á annan hátt, og rekstursskráning er ekki eins einföld og það sem við getum gert staðbundið. Því miður mun rack mini profiler eða venjuleg SQL-skrá ekki segja til um það. okkur allt og bendir ekki á hvaða hluti fyrirspurnarinnar ber ábyrgð á tilteknu tímabili. Í tilviki GraphQL-Ruby getum við notað viðskiptalausnir sem eru fáanlegar hér: https://graphql-ruby.org/queries/tracing, eða reyna að útbúa okkar eigin rekki. Hér að neðan lítur brotið út eins og staðbundinn rekki.
# lib/my_custom_tracer.rb
class MyCustomTracer 'graphql.lex',
'parse' => 'graphql.parse',
'validate' => 'graphql.validate',
'analyze_query' => 'graphql.analyze_query',
'analyze_multiplex' => 'graphql.analyze_multiplex',
'execute_multiplex' => 'graphql.execute_multiplex',
'execute_query' => 'graphql.execute_query',
'execute_query_lazy' => 'graphql.execute_query_lazy'
}
def platform_trace(platform_key, key, _data, &block)
start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
result = block.call
duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
observe(platform_key, key, duration)
result
end
def platform_field_key(type, field)
"graphql.#{type.graphql_name}.#{field.graphql_name}"
end
def platform_authorized_key(type)
"graphql.authorized.#{type.graphql_name}"
end
def platform_resolve_type_key(type)
"graphql.resolve_type.#{type.graphql_name}"
end
def observe(platform_key, key, duration)
return if key == 'authorized'
puts "platform_key: #{platform_key}, key: #{key}, duration: #{(duration * 1000).round(5)} ms".yellow
end
end
Uppsetningin er einnig afar einföld, þú þarft að bæta rekjanleikaupplýsingum við skemað. slóðari (MyCustomTracer.new) stillingu. Eins og í dæminu hér að neðan:
# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
query Types::QueryType
mutation Types::MutationType
use GraphQL::Batch
tracer(MyCustomTracer.new)
...
end
Úttakið úr slíkri rekjanlegri greiningu lítur svona út:
Hóf POST "/graphql" fyrir ::1 þann 2021-06-17 22:02:44 +0200
(0.1ms) SELECT sqlite_version(*)
Vinnsla af GraphqlController#execute sem */*
Breytur: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n}"}}
platform_key: graphql.lex, key: lex, duration: 0.156 ms
platform_key: graphql.parse, key: parse, duration: 0.108 ms
platform_key: graphql.validate, key: validate, duration: 0.537 ms
platform_key: graphql.analyze_query, key: analyze_query, duration: 0.123 ms
platform_key: graphql.analyze_multiplex, key: analyze_multiplex, duration: 0.159 ms
Link Load (0.4ms) SELECT "links".* FROM "links"
↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, key: execute_query, duration: 15.562 ms
↳ app/graphql/loaders/record_loader.rb:12:in `perform'
↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
platform_key: graphql.execute_query_lazy, key: execute_query_lazy, duration: 14.12 ms
platform_key: graphql.execute_multiplex, key: execute_multiplex, duration: 31.11 ms
Lokið 200 OK á 48ms (Skoðanir: 1.2ms | ActiveRecord: 2.0ms | Úthlutanir: 40128)
Yfirlit
GraphQL er ekki lengur ný tækni, en lausnir við vandamálum hennar eru ekki fullkomlega staðlaðar ef þær eru ekki hluti af bókasafninu. Innleiðing þessarar tækni í verkefni veitir margar tækifæri til að eiga samskipti við framenda og ég tel persónulega að þetta sé ný gæði í samanburði við það sem REST forritaskil býður upp á.