一、什么是GraphQL
GraphQL 是一個(gè)用于 API 的查詢語言,是一個(gè)使用基于類型系統(tǒng)來執(zhí)行查詢的服務(wù)端運(yùn)行時(shí)(類型系統(tǒng)由你的數(shù)據(jù)定義)。GraphQL 并沒有和任何特定數(shù)據(jù)庫或者存儲(chǔ)引擎綁定,而是依靠你現(xiàn)有的代碼和數(shù)據(jù)支撐,GraphQL 可以運(yùn)行在任何后端框架或者編程語言之上。
GraphQL 全稱叫 Graph Query Language,官方宣傳語是“為你的 API 量身定制的查詢語言”。
用傳統(tǒng)的方式來解釋就是:相當(dāng)于將你所有后端 API 組成的集合看成一個(gè)數(shù)據(jù)庫,用戶終端發(fā)送一個(gè)查詢語句,你的 GraphQL 服務(wù)解析這條語句并通過一系列規(guī)則從你的“ API 數(shù)據(jù)庫”里面將查詢的數(shù)據(jù)結(jié)果返回給終端,而 GraphQL 就相當(dāng)于這個(gè)系統(tǒng)的一個(gè)查詢語言。
二、存在的問題:
REST API :服務(wù)端決定有哪些數(shù)據(jù)返回,客戶端只能挑選使用,如果數(shù)據(jù)過于冗余也只能默默接收再對(duì)數(shù)據(jù)進(jìn)行處理;而數(shù)據(jù)不能滿足需求則需要請(qǐng)求更多的接口。以上就是我們常說的“過渡獲取”和“欠缺獲取”
由于"過度"和"欠缺"的獲取問題及其對(duì)客戶端應(yīng)用程序性能的影響,促進(jìn)有效獲取的 API 技術(shù)才有機(jī)會(huì)在市場(chǎng)上引起轟動(dòng) —— GraphQL 大膽地介入并填補(bǔ)了這一空白。
舉個(gè)簡(jiǎn)單的例子
graphQL 查詢語句
{
getCategories {
id
name
products {
id
name
}
}
}
輸出結(jié)果
{
"data": {
"getCategories": [
{
"id": "1",
"name": "吃的",
"products": [
{
"id": "1",
"name": "浪味仙"
}
]
},
{
"id": "2",
"name": "喝的",
"products": [
{
"id": "4",
"name": "茶顏悅色"
}
]
}
]
}
}
客戶端現(xiàn)在不想要商品信息了 只需要獲取商品分類,你只需要將你的查詢語句修改一下即可
{
getCategories {
id
name
}
}
輸出結(jié)果
{
"data": {
"getCategories": [
{
"id": "1",
"name": "吃的"
},
{
"id": "2",
"name": "喝的"
}
]
}
}
現(xiàn)在我想要通過這一個(gè)查詢,獲取到商品分類,以及所有商品的信息
{
getCategories {
id
name
}
getProducts {
id
name
}
}
輸出結(jié)果
{
"data": {
"getCategories": [
{
"id": "1",
"name": "吃的"
},
{
"id": "2",
"name": "喝的"
}
],
"getProducts": [
{
"id": "1",
"name": "浪味仙"
},
{
"id": "4",
"name": "茶顏悅色"
}
]
}
}
是不是很方便,通過上面例子,可以發(fā)現(xiàn)GraphQL API有如下優(yōu)點(diǎn):客戶端可以自定義查詢語句,數(shù)據(jù)獲取靈活多變,服務(wù)端按需返回?cái)?shù)據(jù),減少網(wǎng)絡(luò)的開銷,提高了性能。而這些都是Restful API的弊端。
三、基礎(chǔ)概念
在介紹GraphQL的使用之前先要了解一些概念
1.GraphQL 類型系統(tǒng)(Type System)
類型系統(tǒng) 是整個(gè) GraphQL 的核心,它用來定義每個(gè)查詢對(duì)象和返回對(duì)象的類型。GraphQL的Type簡(jiǎn)單可以分為兩種,一種叫做Scalar Type(標(biāo)量類型),另一種叫做Object Type(對(duì)象類型)。
a)標(biāo)量類型(Scalar Types)
一個(gè)對(duì)象類型有自己的名字和字段,而某些時(shí)候,這些字段必然會(huì)解析到具體數(shù)據(jù)。這就是標(biāo)量類型的來源:它們表示對(duì)應(yīng) GraphQL 查詢的葉子節(jié)點(diǎn)。
標(biāo)量類型這個(gè)概念有點(diǎn)重要,這個(gè)字段是不是一個(gè)標(biāo)量類型,決定了我們這個(gè)查詢是不是還要繼續(xù)往更深層去遞歸查詢。
GraphQL 自帶一組默認(rèn)標(biāo)量類型:
GraphQLInt:有符號(hào) 32 位整數(shù)。
GraphQLFloat:有符號(hào)雙精度浮點(diǎn)值。
GraphQLString:UTF‐8 字符序列。
GraphQLBoolean 或者 false。
GraphQLID:ID 標(biāo)量類型表示一個(gè)唯一標(biāo)識(shí)符。
b)對(duì)象類型
一個(gè) GraphQL schema 中的最基本的組件是對(duì)象類型,表示你可以從服務(wù)上獲取到什么類型的對(duì)象,以及這個(gè)對(duì)象有什么字段。
例如 以下這段代碼中 id 、name 就是標(biāo)量類型。products就是一個(gè)對(duì)象類型
type categories {
id
name
products {
id
name
}
b)Schema
在 GraphQL 中,類型的定義以及查詢本身都是通過 Schema 去定義的。
Schema它是用來描述接口獲取數(shù)據(jù)邏輯的。我們可以將Schema理解為多個(gè)Query組成的一張表。
這里又涉及一個(gè)新的概念Query,GraphQL中使用Query來抽象數(shù)據(jù)的查詢邏輯,當(dāng)前標(biāo)準(zhǔn)下,有三種查詢類型,分別是query(查詢)、mutation(更改)和subscription(訂閱)。
query(查詢):當(dāng)獲取數(shù)據(jù)時(shí),應(yīng)當(dāng)選取Query類型
mutation(更改):當(dāng)嘗試修改數(shù)據(jù)時(shí),應(yīng)當(dāng)使用mutation類型
subscription(訂閱):當(dāng)希望數(shù)據(jù)更改時(shí),可以進(jìn)行消息推送,使用subscription類型
c)Resolver
我們已經(jīng)了解了graphQL的類型系統(tǒng)和schema,那么我們的數(shù)據(jù)到底怎么來呢?答案是來自 Resolver 函數(shù)。
Resolver 的概念非常簡(jiǎn)單。Resolver 對(duì)應(yīng)著 Schema 上的字段,當(dāng)請(qǐng)求體查詢某個(gè)字段時(shí),對(duì)應(yīng)的 Resolver 函數(shù)會(huì)被執(zhí)行,由 Resolver 函數(shù)負(fù)責(zé)到數(shù)據(jù)庫中取得數(shù)據(jù)并返回,最終將請(qǐng)求體中指定的字段返回。
type Movie {
name
genre
}
type Query {
movie: Movie!
}
當(dāng)請(qǐng)求體查詢movie時(shí),同名的 Resolver 必須返回Movie類型的數(shù)據(jù)。當(dāng)然你還可以單獨(dú)為name字段使用獨(dú)立的 Resolver 進(jìn)行解析。
以上是一些最基本的概念,最后我們來詳細(xì)的分析一下,graphQL是如何定義并查詢到數(shù)據(jù)的
服務(wù)端的schema:
//schema.js
let categories = [
{ id: '1', name: '吃的' },
{ id: '2', name: '喝的' },
]
let products = [
{ id: '1', name: '浪味仙', category: '1' },
{ id: '4', name: '茶顏悅色', category: '2' },
]
//分類里定義每個(gè)字段
const Category = new GraphQLObjectType({
name: 'category',
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
products: {
type: new GraphQLList(Product),//每個(gè)分類下是一個(gè)數(shù)組,而不是一個(gè)對(duì)象
resolve(parent) {//parent代表上一層的查詢結(jié)果
return products.filter(item => item.category === parent.id)
}
}
})
})
//產(chǎn)品里定義每個(gè)字段
const Product = new GraphQLObjectType({
name: 'product',
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString }
})
})
//查詢接口
const RootQuery = new GraphQLObjectType({
name: 'root',
fields: {
getCategories: {
type: new GraphQLList(Category),
args: {
},
resolve(parent, args) {
return categories
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery,//查詢
mutation: RootMutation//修改
})
1.以上是一個(gè)schema文件, 導(dǎo)出了一個(gè)GraphQLSchema的實(shí)例,GraphQLSchema接收兩個(gè)參數(shù),一個(gè)是query,一個(gè)是mutation。
2.RootQuery是一個(gè)GraphQLObjectType實(shí)例,name屬性非必填,這個(gè)屬性是最后生成接口文檔的query接口名字。
3.fields是解析函數(shù),在這里可以理解為查詢方法。
4.getCategories就是我們定義查詢的名稱了。
5.getCategories中有個(gè)type,定義了你使用getCategories能查詢到的所有字段。當(dāng)前查詢結(jié)果是一個(gè)GraphQLList類型,也就是一個(gè)數(shù)組類型,數(shù)組的每一項(xiàng)就是Category。
6.resolve中的內(nèi)容為查詢返回的數(shù)據(jù)。
客戶端對(duì)應(yīng)的查詢語法:
query{
getCategories {
id,
name,
products{
id,
name,
}
}
}
1.首先進(jìn)行第一層解析,查詢類型是query同時(shí)需要它的子query名是getCategories。
2.使用getCategories的Resolver獲取解析數(shù)據(jù),第一層解析完畢
3.之后對(duì)第一層解析的返回值,進(jìn)行第二層解析,當(dāng)前getCategories還包含3個(gè)屬性需要查詢,分別是id、name、product。
3.1 id和name在type Category中為標(biāo)量類型,直接返回getCategories中的對(duì)應(yīng)屬性值。id,name的解析結(jié)束。
3.2product在type Category類型中為對(duì)象類型,于是Granphql嘗試使用Category的Resolver獲取數(shù)據(jù),當(dāng)前field解析完畢。以此類推,直到所有的查詢值都為標(biāo)量的時(shí)候,查詢結(jié)束。
因此上例中的查詢結(jié)果為:
{
"data": {
"getCategories": [
{
"id": "1",
"name": "吃的",
"products": [
{
"id": "1",
"name": "浪味仙"
}
]
},
{
"id": "2",
"name": "喝的",
"products": [
{
"id": "4",
"name": "茶顏悅色"
}
]
}
]
}
}