GraphQL

GraphQLのクエリ言語

マナビト
マナビト
こんにちはマナビトです。
普段はフロントエンドエンジニアとしてJavaScript/TypeScript/Aangularを
メインに開発業務を行なっています。

今回はGraphQLというクエリ言語について実務で使う機会がありましたので、学んだことを記事にしていきたいと思います。

Graphqlのスキーマ設計 以前、GraphQLのクライアント側のクエリについて記事にしましたが、今回はGraphQLのスキーマ設計について記載していきた...

GraphQLとは

GraphQLとはクエリ言語とスキーマ言語で構成されたWeb APIの規格です。クエリ言語を使用することで、サーバーに対してデータを問い合わせることができます。Facebook社によって、2015年にオープンソース化され、JavaやJavaScript、Ruby、Pyhonなど多くの言語に対応しています。

Facebook社が新たにGraphQLを開発することになった背景には、RESTのデータ取得方法にありました。2012年当時、Facbook社はRSETとFacebook Query Language(Facbook用のSQL)でデータテーブルを運用していました。しかしRESTの場合、必要のないデータまで取得してしまうため大量のデータを処理することになり、度々アプリケーションのクラッシュ原因となっていました。アプリケーソンにデータを送る手段を改善するという目的のもと、2012年に開発がスタートし、2015年にGraphQLとして仕様が公開されることになりました。

GraphQLとRESTの違い

クライアントとサーバーを繋げる方法として、先ほど述べたRESTがあります。RESTはWeb APIの設計モデルのことで、データへ容易にアクセスするための原則を基盤にして作られています。この原則に基づいて設計されたAPIがRESTful APIです。
RESTでは、基本的にGET、POST、PUT、の四つのHTTPメゾットを使用して、操作対象のデータをURLエンドポイントによって指定していきます。一方、GraphQLではクエリ言語、スキーマ言語を使用してデータ操作を行なっていきます。

RESTの過剰なデータ取得

RESTの大きな課題として挙げられるのが、必要以上にデータを取得してしまうことです。サーバーからある登場人物の名前だけのデータを受け取りたいとしても、RESTは名前以外に性別、身長、年齢など登録されている全ての情報がレスポンスとして返ってきます。

例えば、一般公開されているSWAPI APIでRESTを使用して、映画の登場人物「ルーク・スカイウォーカー」に関するデータを取得したとします。「https://swapi.co/api/poeple/1」でGETリクエストを投げることで、そのデータを取得できますが、レスポンスのJSONは以下の通りとなります。

{
  "name" : "Luke Skywalker"
  "height": "172"
  "mass": "77"
  "hair_color": "blond"
  "skin_color": "fair"
  "eye_color": "blue"
  "gender": "male"
  "birth_year": "19BBY"
  "homeworld": "https://swapi.co/api/planets/1/"
  "species": [
   "https://swapi.co/api/species/1/"
   ],
   "vehicles": [
   "https://swapi.co/api/vehicles/14/",
   "https://swapi.co/api/vehicles/30/"
  ],
}

もし取得したい情報が、名前と身長と体重だけだった場合、レスポンに不要なデータが多くなってしまい、処理が大変になります。

GraphQLの場合は、取得したい情報(名前と身長と体重)のみをクエリに記載して要求したデータだけレスポンスに含ませることができます。

query {
 person(personID:1){
    name
    height
    mass
  }
}

名前と身長と体重のみが表示されたレスポンス結果

{
  "data": {
    "person": {
      "name": "Luke Skywalker",
      "hieght": 172,
      "mass": 77
    }
  }
}

この様に、GraphQLの場合はクエリを使用して必要なデータだけを指定し、データを取得することができます。RESTと違い余分なデータを取得しないことで、より高速にレスポンス結果を受け取ることができます。

RESTにおけるエンドポイント管理

もう一つのRESTの課題がエンドポイントの自由度の低さです。クライアントに変更が加わると新しいエンドポイントを作成する必要があり、開発が進むにつれてエンドポイントの数が膨れ上がっていきます。この場合、エンドポイントを新たに作成するたびに、クライアント側とバックエンド側のチームが仕様のすり合わせを行わなければならず、開発速度が低下する原因となります。

GraphQLの場合、基本的に単一のエンドポイントしか存在せず、単一のエンドポイントに送られるクエリに基づいてさまざまなデータを統合していくため、エンドポイントを新たに作成する必要はありません。

GraphQLのクエリ言語


それでは実際にGraphQLを操作していきたいと思います。
GraphQLのAPIを簡単に実行するためのツールがオープンソース化されており、自身が開発したGraphQL APIを実行して、挙動の確認を簡単に行うことができます。
今回は、下記サイトを使用して、GraphQLのクエリ文法を試していきたいと思います。

このスノートゥースは、GraphQLを使ってリフトや道路の情報をリアルタイムで取得することができ、GraphQLのクエリを使用することで、稼働状況を操作することができます。
左側にクエリを書くためパネルと、右側にクエリを実行した結果(JSONフォーマットのレスポンス結果)が表示されます。

このサービスではqueryを使用して、GraphQLサーバからデータを取得することができます。取得したいデータはfeildで指定します。

query {
  allLifts {
    name
    status
  }
}

上記のように記述すると、GraphQLサーバーからqueryで指定したデータが返ってきます。サーバーから送られてくるデータ形式はqueryで指定したfeildと同一になります(ここでは各allLiftsのnameとstatusが返ってきます)。

{
  "data": {
    "allLifts": [
      {
        "name": "Astra Express",
        "status": "OPEN"
      },
      {
        "name": "Jazz Cat",
        "status": "CLOSED"
      },
      {
        "name": "Jolly Roger",
        "status": "OPEN"
      },
 ・ 
 ・
 ・
 以下略


GraphQLのクエリ実行に成功すると、dataというキーが含まれたJSONレスポンスが返ってきます。一方でクエリに問題があり、データ取得に失敗した場合はerrorsというキーが含まれてレスポンスが返ってきます。詳細は全てerrorsの中に記録されます。(エラーメッセージを見るとalllifts → allLiftsの記載間違いを指摘されています。)

{
  "error": {
    "errors": [
      {
        "message": "Cannot query field \"alllifts\" on type \"Query\". Did you mean \"allLifts\" or \"Lift\"?",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ],

このようにGraphQLでは一つのqueryであらゆる種類のデータを受け取ることができます。必要に応じてデータを選択して取得したり、省略することができます。

クエリ引数

GraphQLで受け取るデータをフィルタリングしたい場合は、クエリ引数を利用します。引数はクエリのフィールドに関連するキーと値の組み合わせで表現できます。例えばstatusがCLOSEDのリフトを取得したいとすれば、下記のように記述することになります。

query closedLifts {
  allLifts(status:CLOSED) {
    name
    status
  }
}

これでstatusがCLOSEDになっているデータだけを取得することができます。

{
  "data": {
    "allLifts": [
      {
        "name": "Panorama",
        "status": "CLOSED"
      },
      {
        "name": "Summit",
        "status": "CLOSED"
      },
      {
        "name": "Western States",
        "status": "CLOSED"
      }
    ]
  }
}

ルート型(Query 型と Mutation 型、Subscription型)

GraphQLはSQLと同じ問い合わせ言語(クエリ言語)であり、データを取得、変更や削除をすることができます。SQLではSELECT、INSERT、UPDATE、DELETEの四つのコマンドを使用してデータベース上のデータを取得、更新、削除等を行っています。RESTもこの考え方を引き継いでいて、GET、POST、PUT、DELETEの四つのHTTPメゾットを使用して、データを操作します。(ただし操作対象のデータをクエリ言語ではなくURLのエンドポイントによって指定します。)GraphQLでも同様の操作を行うことができます。

SQLとGraphQLの異なる点はSQLはデータベース上のデータを対象とし、GraphQLはインターネット上のデータを操作するという点です。SQLのSELECTの代わりに、GraphQLではQueryを使用してデータを取得します。INSERT、UPDATE、DELETEはMutationを使用します(これ以外にデータの変更を監視するためにSubscriptionもあります)。

データSQLGraophQL
データの取得SELECTQuery
データの登録INSERTMutation
データの更新UPDATEMutation
データの削除DELETEMutation

データを取得する際は、これまで実施した通り、Queryを使用します。

query liftAndTrails {
  open: liftCount(status: OPEN)
  chairlifts: allLifts {
   liftName: name
    status 
  }
}

取得結果....

{
  "data": {
    "open": 7,
    "chairlifts": [
      {
        "liftName": "Astra Express",
        "status": "OPEN"
      },
      {
        "liftName": "Jazz Cat",
        "status": "OPEN"
      },
      {
        "liftName": "Jolly Roger",
        "status": "OPEN"
      },
 ・
 ・
 ・
}

データの書き込み等の操作はMutationを使用することで行えます。例えば新しいデータを作成する場合、下記のように記述することができます。

mutation createSong {
  addSong(title:"No Scrubs", numberOne: true, performerName: "TLC") {
      id
      title
      name
      numberOne
  }
}

この例では、新曲のデータを新しく作成して、Mutationの引数として、title、numberOne、performerNameの三つの変数を指定しています。Mutationが完了すると、id、title、name、numberOneのフィールドをMutation後に受け取ることができます。

既に存在しているデータに変更を加える場合は、引数に文字列を指定して、データを書き換えることができます。

mutation claseLift {
   setliftStatus(id: "jazz-cat" status: CLOSED) {
      name
      status
   }
}

クエリ変数

データを書き換えるMutationは変数を使用することもできます。変数を使用することで、クエリ内の静的な値を動的な値として渡すことができます。変数名の場合は、頭に$がつきます。

mutation createSong($title:String! $numberOne:Int $by:String!) {
 addSong(title:$title, numberOne:$numberOne, performerName:$by) {
     id
     title
     numberOne
  }
}

スカラー型とオブジェクト型

GraphQLにはスカラー型オブジェクト型が存在します。スカラー型とは、プリミティブ型に近い概念で、合計で5つのスカラー型が用意されています。

  • String(文字列型)
  • Int(整数型)
  • Float(浮動小数点型)
  • Boolean(論理型)
  • ID(ID型)

整数型と浮動小数点はJSONではnumberで表現されます。文字列型とID型はStringで表現されます。

オブジェクト型は1つ以上のスキーマで定義されているフィールドの集合のことです。JSONの様に入れ子にすることができる。

type Movie {
    id: ID! ←スカラー型(ID)
    movieTitle: String ←スカラー型(String)
    production: Int ←スカラー型(Int)
    visitors: Int ←スカラー型(Int)
    directedBy: directedBy ←オブジェクト型(下記directedByスキーマにあるようにフィールドの集合)
}

type directedBy {
    id: ID! ←スカラー型(ID)
    admission: Boolean ←論理型(Boolean)
}

ユニオン型(Union型)

オブジェクトのリストを取得する方法で、複数の型のうち、一つを返す型のことをユニオン型といいます。
例えば、以下の例だとクエリにagendaを指定して、Teacher型かStudent型のどちらかが返却されることになります。

query schedule {
  agenda {
   ...on Teacher {
      name
      reps
  }
  ...on Student {
      name
      subject
      students
   }
  }
}

インターフェイス

GraphQLの型定義はインターフェイスを実装することもできます。

interface Book {
    book: ID!
    Title: String!
    publish: number
    auther: String
}

type TextBook implements Book {
    title: String!,
    list: [String!]!
    text: [String!]!
}

type Query {
   BookList: [Book]
}

このオブジェクトはインターフェイスを複数実装することが可能です。複数実装するためには「&」を使用します。

interface NameEntity {
   id: ID
   name: String,
}

interface ValueEntity {
   value: Int
}

type Person implements NameEntity {
    id: ID,
    name: String,
    age: Int
}

type Business implements NameEntitiy & ValueEntity {
   name: String,
   balue: Int,
   sample: [number]
}

参考文献: http://spec.graphql.org/June2018/#sec-Objects

本日はここまでとなります。
最後まで読んでいただきありがとうございました。

【 参考文献 】

■ おすすめのGraphQL書籍