GraphQL
在近幾年被提到的次數愈來愈多,最近參加過的幾回技術大會前端分會場均提到過。對於這種光看名字並不容易想到它是什麼的東西,仍是存在些神祕感的。因而,打算去了解一下GraphQL
究竟是什麼。php
首先,GraphQL
來自Facebook,若是你也跟我同樣徹底沒了解過它,不知道它究竟是幹什麼的,那麼你必定據說過另外一個叫作 Structured QL
的東西。WHAT? 其實就是SQL了。前端
SQL
同樣,GraphQL
是一門查詢語言(Query Language)SQL
同樣的是,GraphQL
也是一套規範,就像MySQL
是SQL
的一套實現同樣,Apollo
, Relay
...也是GraphQL
規範的實現SQL
不一樣的是,SQL
的數據源是數據庫,而GraphQL的數據源能夠是各類各樣的REST API,能夠是各類服務/微服務,甚至能夠是數據庫這裏借Apollo官網的一張圖來講明GraphQL在互聯網應用架構中所處的位置python
Graph是圖的意思,在GraphQL
的世界裏,萬物皆爲圖,也就是說你能夠把你的業務模型建模爲圖。git
圖是將不少真實世界現象變成模型的強大工具,由於它們和咱們天然的心智模型和基本過程的口頭描述很類似。github
這裏就涉及到了圖論,Graph Database之類的知識,感興趣能夠某歌學習一下。數據庫
這樣咱們能夠對GraphQL大致有了一個概念了。那麼咱們來大概瞭解一下GraphQL。後端
簡單的說,站在前端角度,不少文章都會提到過爲了取代Restful API,稍微具體點:api
在知道以上這些優勢是如何作到的以前,咱們先來對GraphQL作一個簡單的學習數組
作爲一名前端開發人員,只站在前端的角度上來講,更多的時候,咱們須要關心的只有兩個操做:緩存
query: 在GraphQL中這個關鍵字屬於schema(能夠理解爲協議)中的一種,表明你要執行的是查詢動做,即增刪改查中的查
mutation: 表明你要執行的動做是增刪改
query
和mutation
統稱爲schema。其實還有一個subscriptions在2017年被加入到規範(spec)中,讓咱們能夠更輕鬆的實現推送功能
這裏咱們以一個公司內部的分享平臺的兩個場景爲例,來介紹一下這兩個操做如何使用。
首先,最基礎的一個場景,分享平臺首頁須要調一個接口,獲取所有的分享列表,目前這個接口的調用方式是:
GET /api/share/allShares
複製代碼
返回
{
"shares": [
{
"shareId": 1238272,
"title": "分享一下Vue3.0",
"desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
"where": "6F-19會議室",
"startTime": 1548842400
},
{
"shareId": 1238272,
"title": "用flutter寫app頁面是一種什麼樣的體驗",
"desc": "用跨平臺框架flutter來寫app頁面的初體驗",
"where": "6F-17會議室",
"startTime": 1548842400
},
{
"shareId": 1238272,
"title": "Cordova原理",
"desc": "一塊兒來了解一下Cordova",
"where": "6F-19會議室",
"startTime": 1548842400
}
]
}
複製代碼
那麼換成GraphQL的方式,咱們能夠這麼寫
query {
shares {
title
desc
where
startTime
}
}
複製代碼
咦?發現漏掉了一個字段,若是咱們要跳至詳情頁,須要知道分享的id,改造一下
# 給一個查詢起一個名字是一個好習慣
query findAllShares {
shares {
# 爲id起了一個別名,叫shareId
shareId: id
title
desc
where
startTime
}
}
複製代碼
到此,一個基礎的查詢操做就完成了。
一般,若是列表類數據量比較大的話,咱們會採用分頁的方式獲取數據,而非一次性獲取所有數據,依然以剛纔的分享列表獲取爲例,若是用傳統的接口調用的方式,一般是要這樣去調接口:
GET /api/share/allShares?star=0&limit=10
複製代碼
返回
{
"allShares": {
"totalCount": 3,
"shares": [
{
"shareId": 1238272,
"title": "分享一下Vue3.0",
"desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
"where": "6F-19會議室",
"startTime": 1548842400
},
{
"shareId": 1238273,
"title": "用flutter寫app頁面是一種什麼樣的體驗",
"desc": "用跨平臺框架flutter來寫app頁面的初體驗",
"where": "6F-17會議室",
"startTime": 1548842400
},
{
"shareId": 1238274,
"title": "Cordova原理",
"desc": "一塊兒來了解一下Cordova",
"where": "6F-19會議室",
"startTime": 1548842400
}
]
}
}
複製代碼
咱們來繼續改造GraphQL的方式,分頁的方式:
# 分頁方式
query findAllShares($start: Int!, $limit: Int = 10) {
allShares (start: $start, limit: $limit) {
totalCount
shares {
shareId: id
title
desc
where
startTime
}
}
}
複製代碼
GraphQL提供了完備的分頁解決方案,可參考 Pagination
下一場景,獲得了全部的分享列表,能夠進入詳情頁了。目前詳情頁有三個主要的查詢接口:獲取分享詳情,獲取分享的評論列表和獲取分享者全部的分享列表。若是是傳統的方式,咱們須要調三個接口:
// 獲取分享的詳情
GET /api/share/:shareId
複製代碼
// 分享詳情返回
{
"shareDetail": {
"shareId": 1238274,
"title": "Cordova原理",
"desc": "一塊兒來了解一下Cordova",
"where": "6F-19會議室",
"startTime": 1548842400,
"attchments": "",
"creatorId": 321,
"lastUpdateTime": 1548842400,
"logoUrl": "",
...
}
}
複製代碼
// 獲取分享的評論列表
GET /api/share/comments/:shareId
複製代碼
// 分享評論列表返回
{
"commentInfo": {
"totalCount": 5,
"comments": [
{
"id": 1,
"content": "很是不錯",
"userId": 213,
"commentTime": 1548842400,
},
{
"id": 2,
"content": "很好",
"userId": 214,
"commentTime": 1548842400,
},
{
"id": 3,
"content": "不錯",
"userId": 216,
"commentTime": 1548842400,
},
{
"id": 4,
"content": "Very GOOD!",
"userId": 2313,
"commentTime": 1548842400,
}
]
}
}
複製代碼
// 分享的建立者的建立的所有分享列表
GET /api/share/shares/:creatorId
複製代碼
// 分享建立者的所有分享返回
{
"hisShares": [
{
"shareId": 1238272,
"title": "分享一下Vue3.0",
"desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
"where": "6F-19會議室",
"startTime": 1548842400
},
{
"shareId": 1238273,
"title": "用flutter寫app頁面是一種什麼樣的體驗",
"desc": "用跨平臺框架flutter來寫app頁面的初體驗",
"where": "6F-17會議室",
"startTime": 1548842400
},
{
"shareId": 1238274,
"title": "Cordova原理",
"desc": "一塊兒來了解一下Cordova",
"where": "6F-19會議室",
"startTime": 1548842400
}
]
}
複製代碼
那麼若是用GraphQL的方式呢?
query shareDetailPage($shareId: Int!, $creatorId:ID!, $start: Int!, $limit: Int = 10) {
# 分享詳情
shareDetail: share (shareId: $shareId) {
shareId: id
title
desc
where
logoUrl
attchments
}
# 評論信息
commentInfo(shareId: $shareId, start: $start, limit: $limit) {
totalCount
comments {
id
userId
content
commentTime
}
}
# TA的分享
hisShares (creatorId: $creatorId) {
shares {
title
desc
where
startTime
}
}
}
複製代碼
一個查詢便可搞定。
變動操做,這裏只介紹一種場景。到了分享詳情頁,咱們可能會須要編輯這個分享,在傳統的方式中,須要調一個更新操做的接口:
POST /api/share/update/:shareId
FormData:
title=xxx&desc=xxx&where=xxx
複製代碼
調完此接口後爲了確認確實已經更新成功了,咱們可能還會調一次獲取分享詳情接口:
GET /api/share/:shareId
複製代碼
接下來咱們換成GraphQL的方式:
mutation editShareInfo($shareObj: ShareInput!) {
editShareInfo(shareInfo: $shareObj) {
shareId: id
title
desc
where
logoUrl
attchments
}
}
複製代碼
這樣,即可以直接將分享內容修改並返回修改後的分享詳情
爲了咱們寫查詢語句部分代碼能有更好的可複用性,GraphQL
還提供了Fragments
(片斷), Inline Fragments
(內聯片斷)和Directives
(指令)功能。前二者能夠類比爲JavaScript中的function
(函數)和anonymous function
(匿名函數),Directives
(指令)能夠根據咱們傳的參數來決定某些字段是否須要返回。這裏就不作過多介紹了。
經過上面的例子,確定會產生些疑問,咱們要如何知道能夠查詢哪些字段?使用哪些參數?這就須要引入schema
了。
通俗點說,schema
就是協議,規範,或者能夠當他是接口文檔。
GraphQL規定,每個schema
有一個根(root)query和根(root)mutation。
咱們先來看Root Query怎麼寫,依然是上面的查詢的例子
# 定義一個根查詢
type Query {
# 能夠查詢的字段和參數
shares(start: Int = 0, limit: Int = 10, creatorId: ID): [Share!]!
share(shareId: ID!): Share!
commentInfo(shareId: ID!, start: Int = 0, limit: Int = 10): CommentInfo!
}
複製代碼
若是你熟悉TypeScript或Flow的話可能會發現上面的寫法似曾相識,是的,裏面的含義就是你想的那樣。每個能夠查詢的字段的參數後面會跟標明這個參數的類型,!
用來表示這個參數不能夠是空的。[]
表示查詢這個字段返回的是數組,[]
裏面是數組的類型。
上面咱們還看到了一些在TypeScript中不存在的類型,好比ID
,ID
咱們暫且把他當成字符串String
類型就能夠了。相似咱們熟悉的JavaScrpit或TypeScript,GraphQL
也有幾個基礎類型,在GraphQL
中他們統稱叫標量類型
(Scalar Type),主要包括:Int(整型), Float(浮點型), String(字符串), Boolean(布爾型)和ID(惟一標識符類型)。同時,GraphQL
也容許你去自定義標量類型,例如:Date類型,只需實現相關的序列化,反序列化和驗證的功能便可。
上面的根查詢定義中,咱們還看到了一些與業務相關的類型,好比Share, Comment,這些統稱爲對象類型
。對象類型也是GraphQL
中的schema
的基本組件,他能夠告訴咱們在服務上能夠得到到哪些對象,以及這個對象有哪些字段。接下來咱們要作的就是定義這些對象類型,直到所有爲基礎類型。
# 定義Share的對象類型
type Share {
id: ID!
title: String!
desc: String!
startTime: Int!
where: String
attchments: String
logoUrl: String
creatorId: ID!
lastUpdateTime: Int
is_delete: Int
score: Int
createTime: Int!
}
# 定義評論信息對象類型
type CommentInfo {
totalCount: Int!
comments: [Comment!]!
}
# 定義評論對象類型
type Comment {
id: ID!
content: String!
commentTime: Int!
userId: ID!
shareId: ID!
}
複製代碼
這樣,咱們就完成了schema的定義。
GraphQL
其實還有Enumeration types
(枚舉類型),Union types
(聯合類型)。同時,爲了代碼能更好的複用,GraphQL
還提供了 Interface
(接口)功能。這裏就不作過多介紹了。
GraphQL
約定,咱們須要爲Root Query(根查詢)和Root Mutation(根變動)裏面的每個字段提供一個resolver
的函數。幷包裝成一個對象暴露出去,就像這樣:
const resolvers = {
// 這裏面寫查詢操做字段的resolver函數
Query: {},
// 這裏面寫變動操做字段的resolver函數
Mutation: {},
}
export default resolvers
複製代碼
讓咱們繼續寫完整:
// 一些加載數據的async function
import { loadSharesFromDB, loadShareById, loadCommentsByShareId } from './datasource'
const resolvers = {
// 這裏面寫查詢操做字段的resolver函數
Query: {
shares: (parent, { start, limit, creatorId }, context, info) => {
return loadSharesFromDB(start, limit, creatorId)
.then(...)
},
share: (parent, { shareId }, context, info) => {
return loadShareById(shareId)
.then(...)
},
commentInfo: (parent, { shareId, start, limit }, context, info) => {
return loadCommentsByShareId(shareId, start, limit)
.then(...)
},
},
// 這裏面寫變動操做字段的resolver函數
Mutation: {
// ...
},
}
複製代碼
一樣的,對於mutation
(變動)操做,咱們也是先把schema
完成:
# 定義Mutation根入口
type Mutation {
editShareInfo(shareInfo: ShareInput!): Share!
}
input ShareInput {
id: ID!
title: String!
desc: String!
where: String
}
複製代碼
而後,補全resolver
函數:
import { updateShareInfo, loadShareById } from './datasource'
const resolvers = {
Query: {
// ...
},
Mutation: {
editShareInfo: (parent, { shareInfo }, context, info) => {
// 更新分享詳情,then獲取更新後的分享詳情
return updateShareInfo(shareInfo.id, shareInfo)
.then(loadShareById(shareInfo.id))
},
},
}
export default resolvers
複製代碼
到此,咱們就實現了這個簡單的GraphQL
的Server了。
GraphQL
還有不少內容能夠探索,使用。好比,若是用Schema構建函數來生成對象類型,能夠標記某一個字段爲廢棄,並給出廢棄緣由。這樣,在版本迭代時,就能夠友好的提示到舊版本的使用者,促使其升級到最新的接口,經過某些檢測手段,咱們也能很輕鬆的知道舊版本的使用頻率,從而方便的讓咱們在某一個時間完全刪掉這個字段。
若是說GraphQL
有什麼缺點,那可能就是上手確實沒那麼容易,並且對於後端同窗來講,仍是有不少坑要踩的,好比緩存,性能問題等。好在目前的GraphQL
的資料已經不像幾年前那樣的匱乏,不論是官方仍是社區,GraphQL
能夠參考的資源和解決方案都愈來愈多了。
無論怎樣,單純的對於前端er來講,若是說上一次前端的技術變革是SPA的普及的話,相信當下一次變革到來時,必定有GraphQL
的影子。
GraphQL server
工具,很是適合初學者去了解GraphQL
,並提供了Codepen的方式在線編輯代碼,本篇中的示例就是在這個工具的基礎上實現的