GraphQL is Facebook’s new query language for fetching application data in a uniform way.
GraphQL並不是一個面向圖資料庫的查詢語言,而是一個數據抽象層,包括數據格式、數據關聯、查詢方式定義與實現等等一攬子的東西。GraphQL也並不是一個具體的後端編程框架,如果將REST看做適合於簡單邏輯的查詢標準,那麼GraphQL可以做一個獨立的抽象層,通過對於多個REST風格的簡單的接口的排列組合提供更多複雜多變的查詢方式。與REST相比,GraphQL定義了更嚴格、可擴展、可維護的數據查詢方式。
GraphQL與之前Netflix出品的Falcor,都是致力於解決相同的問題:如何有效處理日益增長不斷變化的Web/Mobile端複雜的數據需求。筆者一直認為,REST原論文最大的功勞在於前後端分離與無狀態請求,而REST的資源化的請求方式只適合面向簡單的請求,對於具有複雜資源間關聯的請求就有點無能為力。關於這一點,筆者在之前的RARF系列中有過充分的討論。
GraphQL is a specification.
還是需要強調一點,引入GraphQL並不意味著要像之前從Struts遷移到SpringBoot一樣需要去修改你的真實的後端代碼,因此GraphQL可以看做一個業務邏輯層靈活有效地輔助工具。這一點也是GraphQL與原來的REST API最大的差別,舉例而言:
{ latestPost { _id, title, content, author { name }, comments { content, author { name } } }}
這是一個很典型的GraphQL查詢,在查詢中指明了需要返回某個Blog的評論與作者信息,一個典型的返回結果譬如:
{ "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方式,要麼需要前端查詢多次,要麼需要去添加一個新的接口,專門針對前端這種較為特殊的請求進行響應,而這樣又不可避免地導致後端代碼的冗餘,畢竟很有可能這個特殊的請求與返回哪天就被廢了。
ReferenceTutorials & DocsMechanism:原理介紹Practices & ResourcesComparison:框架對比CollectionQuick StartOfficial Quick Start:官方的簡單的Quick Start教程Setup首先創建項目文件夾:
mkdir graphql-democd graphql-demo
然後使用npm安裝必要的依賴:
npm init -fnpm 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 librariesvar graphql = require('graphql');var graphqlHTTP = require('express-graphql');var express = require('express');// Import the data you created abovevar 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'drequire('babel/register');require('./server.js');// server.jsimport 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.jsGraphQL listening at http://0.0.0.0:3000
可以用Curl進行簡單的測試:
$ curl -XPOST http://localhost:3000/graphqlHello!
創建一個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.jsimport { 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 dependenciesimport { graphql } from 'graphql';import bodyParser from 'body-parser';let app = express();let PORT = 3000;// parse POST body as textapp.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 }}
GraphiQLReboot 課程升級了:三個課程均有升級,全新開班日期已定。
「點擊原文」或回復「課程」,即可獲取課程表和開班時間。歡迎諮詢 QQ:279312229
關於Reboot:
專注於網際網路運維開發分享、交流,讓更多的運維工程師更加專注於自動化,為國內公有雲開發、監控、運維貢獻自己的力量。這裡聚集著國內一線網際網路工程師,樂於分享與交流 。發現文章不錯的話請關注我們。
Reboot 官方QQ交流群:238757010
諮詢 QQ:279312229