Da ich mit GraphQL mit Rails6 entwickelt habe, habe ich versucht, die Einführungsmethode und die Verwendung zusammenzufassen. Dieses Mal werde ich das Problem Abfrage / Zuordnung / N + 1 vorstellen.
ruby2.7.1 rails6.0.3 GraphQL
GraphQL ist eine Abfragesprache für API-Anforderungen und verfügt über die folgenden Funktionen.
/ graphql
--Query: Daten abrufen
--Mutation: Erstellen, Aktualisieren, LöschenDer große Unterschied zu REST besteht darin, dass der erste Endpunkt einer ist.
Im Fall von REST gibt es mehrere Endpunkte wie "/ sign_up", "/ users", "/ users / 1",
Für GraphQL ist der einzige Endpunkt / graphql
.
REST erfordert mehrere API-Anforderungen, wenn dies von mehreren Ressourcen benötigt wird. GraphQL hat einen Endpunkt, sodass Sie die benötigten Daten auf einmal erhalten können </ font> und der Code einfach ist.
Es wird von zwei implementiert, der übergeordneten Benutzertabelle und der untergeordneten Post-Tabelle. Nehmen wir eine Eins-zu-Viele-Beziehung an, in der ein Benutzer mehrere Beiträge veröffentlichen kann.
Terminal
$ rails g model User name:string email:string
$ rails g model Post title:string description:string user:references
$ rails db:migrate
Säule | Schimmel |
---|---|
name | string |
string |
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
end
Säule | Schimmel |
---|---|
title | string |
description | string |
post.rb
class Post < ApplicationRecord
belongs_to :user
end
Installieren Sie gem.
Gemgile
gem 'graphql' #hinzufügen
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'graphiql-rails' #Zur Entwicklungsumgebung hinzugefügt
end
config/application.rb
require "sprockets/railtie" #Kommentar
Terminal
$ bundle install
$ rails generate graphql:install #Eine auf GraphQL bezogene Datei wird erstellt
Fügen Sie Folgendes zu route.rb
hinzu
Der Endpunkt ist "/ graphiql" in der Entwicklungsumgebung und "/ graphql" in der Produktionsumgebung.
routes.rb
Rails.application.routes.draw do
if Rails.env.development?
# add the url of your end-point to graphql_path.
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
post '/graphql', to: 'graphql#execute' #Hier erzeugen Schienen graphql:Automatisch generiert durch Installation
end
Bitte beachten Sie die Details unten. Verwendung von GraphQL im Rails 6-API-Modus (einschließlich Fehlergegenmaßnahmen)
GraphQL hat einen Typ (für jedes Modell definiert), und Abfragen werden gemäß diesem Typ ausgeführt, um Daten zu erfassen.
Erstellen Sie einen Benutzertyp mit dem folgenden Befehl.
Wenn Sie !
Hinzufügen, wird Null: false
hinzugefügt.
Terminal
$ rails g graphql:object User id:ID! name:String! email:String!
Die folgenden Dateien werden generiert.
user_type.rb
module Types
class UserType < Types::BaseObject
field :id, ID, null: false # `!`Mit`null:false`Wird hinzugefügt werden.
field :name, String, null: false
field :email, String, null: false
end
end
Erstellen Sie eine Abfrage basierend auf dem zuvor erstellten user_type
.
query_type.rb
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false #Benutzer als Array definieren
def users
User.all #Benutzerliste abrufen
end
end
end
Erstellen Sie Benutzerdaten in der Konsole.
$ rails c
$ > User.create(name: "user1", email: "[email protected]")
$ > User.create(name: "user2", email: "[email protected]")
Nachdem Sie fertig sind, starten Sie den Server und wenden Sie sich an Graphiql. (http: // localhost: 3000 / graphiql)
$ rails s
Führen Sie die folgende Abfrage aus.
query{
users{
id
name
email
}
}
Dann wird eine Antwort im JSON-Format zurückgegeben. Da der Benutzertyp ein Array ist, handelt es sich um ein Array.
{
"data": {
"users": [
{
"id": "1",
"name": "user1",
"email": "[email protected]"
},
{
"id": "2",
"name": "user2",
"email": "[email protected]"
}
]
}
}
Dies ist das Ergebnis der Verbindung zu http: // localhost: 3000 / graphiql und der Ausführung.
Ändern Sie die Abfrage, wenn Sie nicht alle Spaltendaten benötigen. Beispielsweise können Sie nur die ID des Benutzers abrufen.
query{
users{
id
}
}
Antwort
{
"data": {
"users": [
{
"id": "1",
},
{
"id": "2",
}
]
}
}
Versuchen Sie als Nächstes, den Beitrag dem Benutzer zuzuordnen. Erstellen Sie ObjectType und Query for User auf dieselbe Weise.
Terminal
$ rails g graphql:object Post id:ID! title:String! description:String!
Die folgenden Dateien werden generiert.
Fügen Sie field: user, Types :: UserType, null: false
hinzu, um die Daten des Benutzers abzurufen.
post_type.rb
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: false
field :description, String, null: false
field :user, Types::UserType, null: false #Fügen Sie diesen Satz hinzu. gehört_to :So etwas wie Benutzer
end
end
Fügen Sie UserType field: posts, [Types :: PostType], null: false
hinzu.
Da hier mehrere Daten verknüpft sind, definieren Sie sie als Array.
user_type.rb
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :email, String, null: false
field :posts, [Types::PostType], null: false #Fügen Sie diesen Satz hinzu. hat_many :So etwas wie Beiträge
end
end
Terminal
$ rails c
$ > Post.create(title: "title", description: "description", user_id: 1)
query_type.rb
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false
def users
User.all
end
#Fügen Sie Folgendes hinzu
field :posts, [Types::PostType], null: false
def posts
Post.all
end
end
end
Die auszuführende Abfrage. Verschachteln Sie den Beitrag für Benutzer und fordern Sie ihn an.
query{
users{
id
name
email
post{
id
title
description
}
}
}
Bei der Ausführung werden die verschachtelten Post-Daten zurückgegeben. Sie können die Daten von "user.posts" im Fall von REST erhalten.
Wenn Sie die Daten von post.user
benötigen, können Sie diese mit der folgenden Abfrage abrufen.
query{
posts{
id
title
description
user{
id
}
}
}
GraphQL-Abfragen sind baumstrukturiert, sodass Assoziationen für N + 1-Probleme anfällig sind. Daher empfehlen wir Ihnen, Bullet zu installieren, ein Juwel, das N + 1 erkennt.
Gemfile
group :development do
gem 'bullet'
end
Terminal
$ bundle install
Fügen Sie die folgenden Einstellungen zu config / environment / development.rb hinzu
config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.rails_logger = true
end
Überprüfen Sie nach Abschluss der Bullet-Installation, ob N + 1 auftritt.
query{
users{
id
name
email
posts{
id
title
description
}
}
}
Wenn Sie dieselbe Abfrage wie zuvor ausführen, wird das folgende Protokoll angezeigt.
Terminal
Processing by GraphqlController#execute as */*
Parameters: {"query"=>"query{\n users{\n id\n name\n email\n posts{\n id\n title\n description\n }\n }\n}", "variables"=>nil, "graphql"=>{"query"=>"query{\n users{\n id\n name\n email\n posts{\n id\n title\n description\n }\n }\n}", "variables"=>nil}}
User Load (0.2ms) SELECT "users".* FROM "users"
↳ app/controllers/graphql_controller.rb:15:in `execute'
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
↳ app/controllers/graphql_controller.rb:15:in `execute'
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 2]]
↳ app/controllers/graphql_controller.rb:15:in `execute'
Completed 200 OK in 36ms (Views: 0.3ms | ActiveRecord: 1.7ms | Allocations: 18427)
POST /graphql
USE eager loading detected
User => [:posts]
Add to your query: .includes([:posts])
Call stack
N + 1 tritt auf, weil dort "Zu Ihrer Abfrage hinzufügen: .includes ([: posts])" steht. SQL wurde ebenfalls dreimal ausgegeben.
Terminal
User Load (0.2ms) SELECT "users".* FROM "users"
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 2]]
Um N + 1 zu eliminieren, wird wie gewohnt nur "eingeschlossen". Sie können es mit einem Datenlader lösen, aber dieses Mal verwenden wir "Includes". Ändern Sie den Teil, der die Benutzerliste erhält, wie folgt.
query_type.rb
def users
# User.all #Vorher ändern
User.includes(:posts).all #Nach der veränderung
end
Überprüfen wir nun das Protokoll, um festzustellen, ob N + 1 aufgelöst wurde. Die Warnung ist weg.
Terminal
Processing by GraphqlController#execute as */*
Parameters: {"query"=>"query{\n users{\n id\n name\n email\n posts{\n id\n title\n description\n }\n }\n}", "variables"=>nil, "graphql"=>{"query"=>"query{\n users{\n id\n name\n email\n posts{\n id\n title\n description\n }\n }\n}", "variables"=>nil}}
User Load (0.7ms) SELECT "users".* FROM "users"
↳ app/controllers/graphql_controller.rb:15:in `execute'
Post Load (1.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (?, ?) [["user_id", 1], ["user_id", 2]]
↳ app/controllers/graphql_controller.rb:15:in `execute'
Completed 200 OK in 57ms (Views: 0.2ms | ActiveRecord: 1.9ms | Allocations: 15965)
Da die Anzahl der SQL-Anweisungen auf zwei reduziert wurde, wurde N + 1 erfolgreich aufgelöst.
Terminal
User Load (0.7ms) SELECT "users".* FROM "users"
Post Load (1.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (?, ?) [["user_id", 1], ["user_id", 2]]
Wenn Sie den Code normal schreiben, werden Felder und Methoden unabhängig vom Modell zu "query_type" hinzugefügt, sodass "query_type" immer größer wird.
query_type.rb
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false
def users
User.includes(:posts).all
end
field :posts, [Types::PostType], null: false
def posts
Post.all
end
end
end
Das GitHub-Problem enthält bewährte Methoden zum Vermeiden des Aufblähens von query_type.rb mithilfe von Resolver. https://github.com/rmosolgo/graphql-ruby/issues/1825#issuecomment-441306410
Nur das Feld ist in query_type
definiert.
query_type.rb
module Types
class QueryType < BaseObject
field :users, resolver: Resolvers::QueryTypes::UsersResolver
field :posts, resolver: Resolvers::QueryTypes::PostsResolver
end
end
Anschließend wird der Methodenteil für jeden ObjectType in Resolver ausgeschnitten. (Ein neues Resolver-Verzeichnis wurde erstellt.) Wenn Sie vergessen, "GraphQL :: Schema :: Resolver" zu schreiben, tritt ein Fehler auf. Vergessen Sie ihn also nicht.
resolvers/query_types/users_resolver.rb
module Resolvers::QueryTypes
class UsersResolver < GraphQL::Schema::Resolver
type [Types::UserType], null: false
def resolve
User.includes(:posts).all
end
end
end
resolvers/query_types/posts_resolver.rb
module Resolvers::QueryTypes
class PostsResolver < GraphQL::Schema::Resolver
type [Types::PostType], null: false
def resolve
Post.all
end
end
end
GraphQL war unerwartet lang, als ich auch meine Bewertung zusammenfasste. Ich wollte auch über rspec und Mutation schreiben, aber ich würde es gerne beim nächsten Mal tun.
Recommended Posts