[RUBY] [Rails 6] API-Entwicklung mit GraphQL (Query)

Einführung

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.

Entwicklungsumgebung

ruby2.7.1 rails6.0.3 GraphQL

1. Was ist GraphQL?

GraphQL ist eine Abfragesprache für API-Anforderungen und verfügt über die folgenden Funktionen.

Der 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.

2. Die diesmal verwendete Tabelle

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

Benutzertabelle

Säule Schimmel
name string
email string

user.rb


class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end

Posttabelle

Säule Schimmel
title string
description string

post.rb


class Post < ApplicationRecord
  belongs_to :user
end

3. Führen Sie GraphQL in Schienen ein

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)

4. Abfrage, um die Benutzerliste zu erhalten

GraphQL hat einen Typ (für jedes Modell definiert), und Abfragen werden gemäß diesem Typ ausgeführt, um Daten zu erfassen.

Erstellen Sie ObjectType für Benutzer

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

Benutzerabfrage erstellen

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]")

Abfrage ausführen

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. スクリーンショット 2020-09-27 20.32.34.png

Beim Anfordern und Empfangen nur der notwendigen Daten

Ä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",
      }
    ]
  }
}

5. Verein

Versuchen Sie als Nächstes, den Beitrag dem Benutzer zuzuordnen. Erstellen Sie ObjectType und Query for User auf dieselbe Weise.

Generieren Sie ObjectType für Post

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)

Post-Abfrage generieren

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

Abfrage ausführen

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.

スクリーンショット 2020-09-27 21.00.34.png

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
    }
  }
}

スクリーンショット 2020-09-28 9.54.16.png

6. Führen Sie Bullet ein, um N + 1 zu erkennen

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.

Installieren Sie Bullet

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 N + 1

Ü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]]

So eliminieren Sie N + 1

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]]

7. Schneiden Sie den Resolver aus

Normale Abfrage

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

Abfrage mit Resolver

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

Am Ende

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

[Rails 6] API-Entwicklung mit GraphQL (Query)
Hinweise zur MOD-Entwicklung mit Minecraft 14.4 Fabric API # 1
Rails API-Serverumgebungskonstruktion mit Docker-Compose
So erstellen Sie eine Abfrage mithilfe von Variablen in GraphQL [Verwenden von Ruby on Rails]
So erstellen Sie eine API mit GraphQL und Rails
Verwenden der PAY.JP-API mit Rails ~ Implementierungsvorbereitung ~ (payjp.js v2)
[Rails] Entwicklung mit MySQL
Verwenden der PAY.JP-API mit Rails ~ Kartenregistrierung ~ (payjp.js v2)
Entwicklung von DSL mit ANTLR 4.7.1
Versuchen Sie es mit dem Ruby on Rails-Abfrageattribut
[Rails API + Vue] Laden Sie Bilder mit Active Storage hoch und zeigen Sie sie an
[Rails] Erstellen Sie einen Echo-Bot mit der LINE Messaging-API.
Zeigen Sie die API-Definition in der Swagger-Benutzeroberfläche mit Docker + Rails6 + apipie an
Aufbau der Rails6-Entwicklungsumgebung [Mac]
Suchfunktion mit [Rails] Ransack
SNS-Authentifizierung mit Rails Google
[Schienen] Speichern Sie Bilder mit Carrierwave
Japanisieren Sie mit i18n mit Rails
[Rails] Japanische Lokalisierung mit Rails-i18n
[Rails] Testcode mit Rspec
Fehler bei der Verwendung von Schienen Capybara
[Rails] Versuchen Sie, Faradays Middleware zu verwenden
Detaillierte Tipps zur Verwendung von Rails
[Rails 6] Sternförmige Überprüfung mit Raty.js