GraphQL初探:從REST到GraphQL,更完善的數據查詢定義

Github 系列文章Repo前端

Introduction

GraphQL is Facebook’s new query language for fetching application data in a uniform way.node

GraphQL並非一個面向圖數據庫的查詢語言,而是一個數據抽象層,包括數據格式、數據關聯、查詢方式定義與實現等等一攬子的東西。GraphQL也並非一個具體的後端編程框架,若是將REST看作適合於簡單邏輯的查詢標準,那麼GraphQL能夠作一個獨立的抽象層,經過對於多個REST風格的簡單的接口的排列組合提供更多複雜多變的查詢方式。與REST相比,GraphQL定義了更嚴格、可擴展、可維護的數據查詢方式。git

GraphQL與以前Netflix出品的Falcor,都是致力於解決相同的問題:如何有效處理日益增加不斷變化的Web/Mobile端複雜的數據需求。筆者一直認爲,REST原論文最大的功勞在於先後端分離與無狀態請求,而REST的資源化的請求方式只適合面向簡單的請求,對於具備複雜資源間關聯的請求就有點無能爲力。關於這一點,筆者在以前的RARF系列中有過充分的討論。github

GraphQL is a specification.shell

仍是須要強調一點,引入GraphQL並不意味着要像以前從Struts遷移到SpringBoot同樣須要去修改你的真實的後端代碼,所以GraphQL能夠看作一個業務邏輯層靈活有效地輔助工具。這一點也是GraphQL與原來的REST API最大的差異,舉例而言:數據庫

{

  latestPost {

    _id,

    title,

    content,

    author {

      name

    },

    comments {

      content,

      author {

        name

      }

    }

  }

}

這是一個很典型的GraphQL查詢,在查詢中指明瞭須要返回某個Blog的評論與做者信息,一個典型的返回結果譬如:express

{

  "data": {

    "latestPost": {

      "_id": "03390abb5570ce03ae524397d215713b",

      "title": "New Feature: Tracking Error Status with Kadira",

      "content": "Here is a common feedback we received from our users ...",

      "author": {

        "name": "Pahan Sarathchandra"

      },

      "comments": [

        {

          "content": "This is a very good blog post",

          "author": {

            "name": "Arunoda Susiripala"

          }

        },

        {

          "content": "Keep up the good work",

          "author": {

            "name": "Kasun Indi"

          }

        }

      ]

    }

  }

}

而若是採用REST API方式,要麼須要前端查詢屢次,要麼須要去添加一個新的接口,專門針對前端這種較爲特殊的請求進行響應,而這樣又不可避免地致使後端代碼的冗餘,畢竟頗有可能這個特殊的請求與返回哪天就被廢了。npm

Reference

Tutorials & Docs

Mechanism:原理介紹

Practices & Resources

Comparison:框架對比

Collection

Quick Start

Official Quick Start:官方的簡單的Quick Start教程

Setup

首先建立項目文件夾:

mkdir graphql-demo
cd graphql-demo

而後使用npm安裝必要的依賴:

npm init -f
npm install graphql express express-graphql --save

Data

做爲一個簡單的數據服務器,咱們僅使用最簡單的JSON文件做爲數據源:

{
  "1": {
    "id": "1",
    "name": "Dan"
  },
  "2": {
    "id": "2",
    "name": "Marie"
  },
  "3": {
    "id": "3",
    "name": "Jessie"
  }
}

Server

一個簡單的GraphQL服務器須要建立Scheme以及支持的查詢:

// Import the required libraries
var graphql = require('graphql');
var graphqlHTTP = require('express-graphql');
var express = require('express');

// Import the data you created above
var data = require('./data.json');

// Define the User type with two string fields: `id` and `name`.
// The type of User is GraphQLObjectType, which has child fields
// with their own types (in this case, GraphQLString).
var userType = new graphql.GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

// Define the schema with one top-level field, `user`, that
// takes an `id` argument and returns the User with that ID.
// Note that the `query` is a GraphQLObjectType, just like User.
// The `user` field, however, is a userType, which we defined above.
var schema = new graphql.GraphQLSchema({
  query: new graphql.GraphQLObjectType({
    name: 'Query',
    fields: {
      user: {
        type: userType,
        // `args` describes the arguments that the `user` query accepts
        args: {
          id: { type: graphql.GraphQLString }
        },
        // The resolve function describes how to "resolve" or fulfill
        // the incoming query.
        // In this case we use the `id` argument from above as a key
        // to get the User from `data`
        resolve: function (_, args) {
          return data[args.id];
        }
      }
    }
  })
});

express()
  .use('/graphql', graphqlHTTP({ schema: schema, pretty: true }))
  .listen(3000);

console.log('GraphQL server running on http://localhost:3000/graphql');

而後使用node命令啓動服務器:

node index.js

若是你直接訪問http://localhost:3000/graphql會獲得以下反饋:

{
  "errors": [
    {
      "message": "Must provide query string."
    }
  ]
}

Queries

按照以下方式能夠建立一個簡單的根據ID查詢用戶的姓名,從中能夠看出基本的GraphQL的查詢的樣式,就是一個JSON的Key-Value對,鍵值就是查詢值:

{

  user(id: "1") {

    name

  }

}

返回數據是:

{

  "data": {

    "user": {

      "name": "Dan"

    }

  }

}

若是你但願以GET方式進行查詢,能夠移除全部的空格,即獲得以下方式的請求:

http://localhost:3000/graphql?query={user(id:"1"){name}}

Another First GraphQL Server:另外一個Step By Step的介紹

Setup an HTTP Server:構建一個HTTP服務器

注意,GraphQL定義了一種通用的數據查詢語言,並不必定要基於HTTP協議,不過目前絕大部分應用服務器的交互協議都是HTTP,所以這裏也是基於Express以及GraphQL的JavaScript實現構建一個簡單的GraphQL服務器。

$ mkdir graphql-intro && cd ./graphql-intro

$ npm install express --save

$ npm install babel --save

$ touch ./server.js

$ touch ./index.js

而核心的服務端代碼爲:

// index.js

// by requiring `babel/register`, all of our successive `require`s will be Babel'd

require('babel/register');

require('./server.js');



// server.js

import express from 'express';



let app  = express();

let PORT = 3000;



app.post('/graphql', (req, res) => {

  res.send('Hello!');

});



let server = app.listen(PORT, function () {

  let host = server.address().address;

  let port = server.address().port;



  console.log('GraphQL listening at http://%s:%s', host, port);

});

直接使用Node命令便可以啓動服務器:

$ node index.js
GraphQL listening at http://0.0.0.0:3000

能夠用Curl進行簡單的測試:

$ curl -XPOST http://localhost:3000/graphql

Hello!

建立一個Schema

如今咱們已經建立了一個簡單的HTTP Server能夠進行交互,下面咱們就要爲該Server添加GraphQL查詢的解析的支持。首先回顧下一個基本的GraphQL的查詢請求以下:

query getHighScore { score }

該查詢意味着某個GraphQL的客戶端但願獲取getHighScore域的score子域的信息,Fields就是客戶端要求GraphQL返回的數聽說明,一個Fields也能夠包含參數,譬如:

query getHighScores(limit: 10) { score }

而咱們的GraphQL Server首先須要知道應該如何去解析這樣的請求,即須要去定義Schema。構建一個Schema的過程有點相似於構建RESTful的路由樹的過程,Schema會包含Server能夠返回給前端的Fields以及響應中的數據類型。GraphQL中是採起了靜態數據類型,所以Client能夠依賴於其發起請求時聲明的數據類型。首先咱們聲明使用Schema所須要的依賴項:

$ npm install graphql --save
$ npm install body-parser --save
$ touch ./schema.js

而後咱們建立一個GraphQLSchema實例,通常來講咱們會將配置放入一個單獨的文件夾中:

// schema.js

import {

  GraphQLObjectType,

  GraphQLSchema,

  GraphQLInt

} from 'graphql/lib/type';



let count = 0;



let schema = new GraphQLSchema({

  query: new GraphQLObjectType({

    name: 'RootQueryType',

    fields: {

      count: {

        type: GraphQLInt,

        resolve: function() {

          return count;

        }

      }

    }

  })

});



export default schema;

該Schema的定義用通俗地語言表達便是針對查詢會返回一個RootQueryType的對象,而每一個RootQueryType對象會包含一個整型的count域。

Connect the Schema

在定義好了Schema以後,咱們就須要將其應用到HTTP Server中:

import express from 'express';

import schema from './schema';

// new dependencies

import { graphql } from 'graphql';

import bodyParser from 'body-parser';



let app  = express();

let PORT = 3000;



// parse POST body as text

app.use(bodyParser.text({ type: 'application/graphql' }));



app.post('/graphql', (req, res) => {

  // execute GraphQL!

  graphql(schema, req.body)

  .then((result) => {

    res.send(JSON.stringify(result, null, 2));

  });

});



let server = app.listen(PORT, function () {

  var host = server.address().address;

  var port = server.address().port;



  console.log('GraphQL listening at http://%s:%s', host, port);

});

全部針對/graphql的查詢都會在定義好的Schema下執行,這裏咱們默認的返回count值,仍是使用Curl進行簡單的調試能夠獲得:

$ node ./index.js // restart your server
// in another shell
$ curl -XPOST -H "Content-Type:application/graphql"  -d 'query RootQueryType { count }' http://localhost:3000/graphql
{
 "data": {
   "count": 0
 }
}

Introspect the Server:獲取Server定義的Schema信息

實際上,GraphQL Server也能夠返回其定義好的Schema信息:

$ curl -XPOST -H 'Content-Type:application/graphql'  -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql
{
 "data": {
   "__schema": {
     "queryType": {
       "name": "RootQueryType",
       "fields": [
         {
           "name": "count",
           "description": null
         }
       ]
     }
   }
 }
}

其使用的查詢實際上就是這個樣子:

{
 __schema {
   queryType {
     name, 
     fields {
       name,
       description
     }
   }
 }
}

實際上,咱們也能夠爲每一個定義的域添加譬如description, isDeprecated, 以及 deprecationReason這樣的描述信息,譬如:

let schema = new GraphQLSchema({

  query: new GraphQLObjectType({

    name: 'RootQueryType',

    fields: {

      count: {

        type: GraphQLInt,

        // add the description

        description: 'The count!',

        resolve: function() {

          return count;

        }

      }

    }

  })

});

那麼返回的新的元信息就是:

$ curl -XPOST -H 'Content-Type:application/graphql'  -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql
{
 "data": {
   "__schema": {
     "queryType": {
       "name": "RootQueryType",
       "fields": [
         {
           "name": "count",
           "description": "The count!"
         }
       ]
     }
   }
 }
}

Add a Mutation:GraphQL中支持增刪改

上文中所講的都是基於GraphQL定義一個查詢方式,而GraphQL也是支持對於數據的增刪改,這在GraphQL中稱爲mutations。Mutations也是一個域,其主要是爲了指明某個請求打來的Side Effects,所以大部分的語法仍是一致的。Mutations也是須要提供一個返回值的,主要是爲了返回你改變的值以供驗證修改是否成功。

let schema = new GraphQLSchema({

  query

  mutation: new GraphQLObjectType({

    name: 'RootMutationType',

    fields: {

      updateCount: {

        type: GraphQLInt,

        description: 'Updates the count',

        resolve: function() {

          count += 1;

          return count;

        }

      }

    }

  })

});

對應的查詢方式就是:

$ curl -XPOST -H 'Content-Type:application/graphql' -d 'mutation RootMutationType { updateCount }' http://localhost:3000/graphql
{
 "data": {
   "updateCount": 1
 }
}

GraphiQL

相關文章
相關標籤/搜索