Depuis que j'ai développé en utilisant GraphQL avec rails6, j'ai essayé de résumer la méthode d'introduction et l'utilisation. Cette fois, je présenterai le problème Requête / Association / N + 1.
ruby2.7.1 rails6.0.3 GraphQL
GraphQL est un langage de requête pour les requêtes API et possède les fonctionnalités suivantes.
--Un seul point de terminaison / graphql
--Query: obtenir des données
--Mutation: créer, mettre à jour, supprimer
La grande différence avec REST est que le premier point de terminaison est un.
Dans le cas de REST, il existe plusieurs points de terminaison tels que / sign_up
, / users
, / users / 1
,
Pour GraphQL, le seul point final est / graphql
.
REST nécessite plusieurs demandes d'API lorsque plusieurs ressources l'exigent GraphQL a un point de terminaison, donc vous pouvez obtenir les données dont vous avez besoin en une seule fois </ font> et le code est simple.
Il est implémenté par deux, la table User parent et la table Post enfant. Supposons une relation un-à-plusieurs dans laquelle un utilisateur peut publier plusieurs publications.
Terminal
$ rails g model User name:string email:string
$ rails g model Post title:string description:string user:references
$ rails db:migrate
colonne | Moule |
---|---|
name | string |
string |
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
end
colonne | Moule |
---|---|
title | string |
description | string |
post.rb
class Post < ApplicationRecord
belongs_to :user
end
Installez gem.
Gemgile
gem 'graphql' #ajouter à
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'graphiql-rails' #Ajouté à l'environnement de développement
end
config/application.rb
require "sprockets/railtie" #Décommenter
Terminal
$ bundle install
$ rails generate graphql:install #Un fichier lié à GraphQL est créé
Ajoutez ce qui suit à routes.rb
Le point final est / graphiql
dans l'environnement de développement et / graphql
dans l'environnement de production.
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' #Voici les rails générer graphql:Généré automatiquement par l'installation
end
Veuillez vous référer aux détails ci-dessous. Comment utiliser GraphQL en mode API Rails 6 (y compris les contre-mesures d'erreur)
GraphQL a un Type (défini pour chaque modèle) et les requêtes sont exécutées en fonction de ce Type pour acquérir des données.
Créez un type d'utilisateur avec la commande suivante.
Si vous ajoutez !
, Null: false
sera ajouté.
Terminal
$ rails g graphql:object User id:ID! name:String! email:String!
Les fichiers suivants seront générés.
user_type.rb
module Types
class UserType < Types::BaseObject
field :id, ID, null: false # `!`Avec`null:false`Sera ajouté.
field :name, String, null: false
field :email, String, null: false
end
end
Créez une requête basée sur le ʻuser_type` créé précédemment.
query_type.rb
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false #Définir l'utilisateur comme un tableau
def users
User.all #Obtenir la liste des utilisateurs
end
end
end
Créez des données utilisateur dans la console.
$ rails c
$ > User.create(name: "user1", email: "[email protected]")
$ > User.create(name: "user2", email: "[email protected]")
Maintenant que vous êtes prêt, démarrez le serveur et vérifiez avec Graphiql. (http: // localhost: 3000 / graphiql)
$ rails s
Exécutez la requête suivante.
query{
users{
id
name
email
}
}
Ensuite, une réponse au format json sera renvoyée. Le type d'utilisateurs étant un tableau, il s'agit d'un tableau.
{
"data": {
"users": [
{
"id": "1",
"name": "user1",
"email": "[email protected]"
},
{
"id": "2",
"name": "user2",
"email": "[email protected]"
}
]
}
}
C'est le résultat de la connexion à http: // localhost: 3000 / graphiql et de son exécution.
Modifiez la requête si vous n'avez pas besoin de toutes les données de la colonne. Par exemple, vous ne pouvez obtenir que l'identifiant de l'utilisateur.
query{
users{
id
}
}
réponse
{
"data": {
"users": [
{
"id": "1",
},
{
"id": "2",
}
]
}
}
Ensuite, essayez d'associer le message à l'utilisateur. Créez ObjectType et Query for User de la même manière.
Terminal
$ rails g graphql:object Post id:ID! title:String! description:String!
Les fichiers suivants seront générés.
Ajoutez field: user, Types :: UserType, null: false
pour obtenir les données de l'utilisateur.
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 #Ajoutez cette phrase. fait parti_to :Quelque chose comme utilisateur
end
end
Ajoutez field: posts, [Types :: PostType], null: false
à UserType.
Puisque plusieurs données sont liées ici, définissez-les comme un tableau.
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 #Ajoutez cette phrase. a_many :Quelque chose comme des messages
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
#Ajoutez ce qui suit
field :posts, [Types::PostType], null: false
def posts
Post.all
end
end
end
La requête à exécuter. Imbriquez le message aux utilisateurs et demandez-le.
query{
users{
id
name
email
post{
id
title
description
}
}
}
Une fois exécutées, les données post imbriquées seront renvoyées. Vous pouvez obtenir les données de ʻuser.posts` dans le cas de REST.
Si vous avez besoin des données de post.user
, vous pouvez les obtenir avec la requête suivante.
query{
posts{
id
title
description
user{
id
}
}
}
Les requêtes GraphQL sont structurées en arborescence, les associations sont donc sujettes à des problèmes N + 1. Par conséquent, nous vous recommandons d'installer Bullet, qui est une gemme qui détecte N + 1.
Gemfile
group :development do
gem 'bullet'
end
Terminal
$ bundle install
Ajoutez les paramètres suivants à config / environnements / development.rb
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
Une fois l'installation de Bullet terminée, vérifions si N + 1 se produit.
query{
users{
id
name
email
posts{
id
title
description
}
}
}
Si vous exécutez la même requête qu'auparavant, le journal suivant s'affiche.
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
ʻAjoutez à votre requête: N + 1 se produit car il dit .includes ([: posts]) `. SQL a également été publié trois fois.
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]]
Pour éliminer N + 1, «inclut» comme d'habitude. Vous pouvez le résoudre avec un chargeur de données, mais cette fois, nous utiliserons ʻincludes`. Modifiez la partie qui obtient la liste des utilisateurs comme suit.
query_type.rb
def users
# User.all #Changer avant
User.includes(:posts).all #Après le changement
end
Maintenant, vérifions le journal pour voir si N + 1 a été résolu. L'avertissement est parti.
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)
Comme le nombre d'instructions SQL a été réduit à deux, N + 1 a été résolu avec succès.
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]]
Si vous écrivez le code normalement, les champs et les méthodes seront ajoutés à query_type
quel que soit le modèle, donc query_type
grandira de plus en plus.
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
Le problème GitHub présente les meilleures pratiques pour éviter le gonflement de query_type.rb à l'aide de Resolver. https://github.com/rmosolgo/graphql-ruby/issues/1825#issuecomment-441306410
Seul le champ est défini dans query_type
.
query_type.rb
module Types
class QueryType < BaseObject
field :users, resolver: Resolvers::QueryTypes::UsersResolver
field :posts, resolver: Resolvers::QueryTypes::PostsResolver
end
end
Ensuite, la partie de méthode est découpée en Resolver pour chaque ObjectType. (Un nouveau répertoire de résolveurs a été créé.)
Si vous oubliez d'écrire GraphQL :: Schema :: Resolver
, une erreur se produira, alors faites attention à ne pas l'oublier.
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 a été étonnamment long lorsque j'ai également résumé ma critique. Je voulais aussi écrire sur rspec et mutation, mais j'aimerais le faire la prochaine fois.
Recommended Posts