架好 local project 後,我們就來聊聊組成 GraphQL Server 的三大靈魂: Schema 、 Resolver 以及 Web Server!
Web Server 在昨天的 project 中很明顯的被 ApolloServer
解決了,今天要處理 Schema 與 Resolver 兩個與 API 設計相關的概念,並且開始實作簡單社交軟體的第一步:取得 user 資料。
PS 因為網路資源多以英文為主,所以在專有名詞上我會盡量以英文為主以利各位未來學習,但很多名詞其實都沒有一個確切的英文名稱,所以我會試著選用概念相近的單字,若有不當的用法還肯請指正。
要組成一個 GraphQL API 需要一個架構跟實作架構的行為:
來打個比方,今天你進去麥當勞會先抬頭注意牆上的 菜單,這個菜單就如同 GraphQL 的 Schema,一一列出關於「你可以點的食物 (查詢的資料)」、「餐點與餐點的套餐搭配 (資料間的關聯)」、「套餐的內容物 (資料的架構)」等的資訊。
一個 GraphQL Schema 是由很多的 type 、 type 之間的關聯以及資料結構所組成
想好餐點後、點完餐,後面的廚房就開使針對你的餐點 料理出你要的食物 (拿出你要的資料),也就是 Resolvers 在做的事情。
Resolver 的工作就是基於 Schema 的設計來完成資料取得 & 計算的任務
簡而言之,Schema 是由 GraphQL 的 type 所組成的 Type system ,Resolver 則是對應 Schema 的需求來取得與計算所需的資料,也可當作是 GraphQL API 的實作。
小問題 1:如果 Schema 是菜單、 Resolver 是廚房,那點餐的你是?
小問題 2: 所以 business logic 也是 Resolver 的守備範圍囉?
答案在文章末
Schema 就是你的 API 的模樣,也是你實現你的資料模型的地方,以下是 Scheam Definition 示意圖:
有了一個基本概念,我們就來介紹 Schema 中最重要的概念:
再複習一次,什麼是 Type 呢?中文翻譯就是「型別」,也就是資料的展現形式。
Type 可以說是建構 GraphQL Schema 的基石,其中最基本的就是 object type,可以回想第一天的範例:
type User {
id: Int
name: String
age: Int
}
type Query {
me: User
}
上面的 User 就是一個自定義的 Object type,裡面有許多自定義的 field (欄位),每個 field 後面都要加上自己的 type 。
這個 type 可以是一個 Object type ,也可以是實際的值如 Integer, String, Boolean 等等,而這些實際的值的型別我們稱為 Scalar Type
。
Scalar Type 與 Object Type 不同的點在於 Scalar Type 會存取實際資料,預設的 Scalar Type 有以下五種:
需要特別注意 ID 方面,只是語意的表達並未實際做什麼檢查,所以傳入 Int 或 String 都可以過。不過實作時通常是傳入 uuid string。
那如果 Resolver 處理出的資料不符合 Type 的要求呢?
很簡單,若 GraphQL 檢查出資料與 Schema 裡某 field 的 Type 不合的話就會直接吐出 Error ,幫你嚴格把關、只接受符合的資料型別!
舉例來說,若 field Type 為 Int 但 Resolver 卻餵給他 "ABC"
, Server 就會吐出 "message": "Int cannot represent non-integer value: \"string\"",
的錯誤。
不過需要特別注意,Null
是可以被接受的。不管是什麼你後面接什麼 Type 預設上都是 Nullable ,因此即使你要 Int 它給你 Null
在 GraphQL 處理時是不會報錯的。
另外也可以自己實作出自定義的 Scalar Type 供特殊資料格式使用,
常見的有 Date
, URL
, Email
, JSON
等等。
(延伸:如何實作 Custom Scalar Type & 現成自定義 Scalar Type npm 套件:GraphQL Custom Types)
前天提過, GraphQL query 基本上就是選取 Object 的 field 來獲取資料,而 Query Type 本身就是一個 Objcet Type 。
Query Type 在使用上跟一般 Object 沒有不同,不過卻有一項特殊任務: 擔當 Schema 的進入點 (entry point)。
有種門神的概念 XD
所以任何 Type 若是想進入 Schema 被 Client 端 query 到,那麼不管身在第幾層也一定要出現在 Query 的資料結構中。
當然門神不只 Query 一個,還有 Mutation ( 資料修改、 sql 中的 CREATE
& UPDATE
) 與 Subscription (訂閱、 sql 中的 TRIGGER
) 兩位大門神,而能收服這三個門神當作 field 來使用的就是最外層的 Root Types
。
可以參考 GitHub GraphQL API Explorer。
其中右邊 Document Explorer 上面有一個 Root Types
的字樣,意思就是「最上 (外) 層的 Type」。如圖:
另外也可以登入自己的 GitHub 帳號玩一玩, 試著在左側輸入
query {
viewer {
login
name
}
}
可得到
{
"data": {
"viewer": {
"login": "自己的登入帳號",
"name": "自己的 GitHub 帳戶名"
}
}
}
小問題3: 所以 Root Type
算是一種 Object Type 嗎 ?
Resolver 部分很簡單,就讓我們接續昨天的例子,順便練習今天所學的部分:
一樣在 index.js
:
User
type 並在 Query
type 中加入 me
fieldme
field 處理的 function,注意名稱一定要對上,目前先回傳第一名使用者const { ApolloServer, gql } = require('apollo-server');
// 1. 加入假資料
const users = [
{
id: 1,
name: 'Fong',
age: 23
},
{
id: 2,
name: 'Kevin',
age: 40
},
{
id: 3,
name: 'Mary',
age: 18
}
];
// The GraphQL schema
// 2. 新增 User type 、在 Query 中新增 me field
const typeDefs = gql`
"""
使用者資訊
"""
type User {
"識別碼"
id: ID
"名字"
name: String
"年齡"
age: Int
}
type Query {
"A simple type for getting started!"
hello: String
"取得當下使用者"
me: User
}
`;
// A map of functions which return data for the schema.
const resolvers = {
Query: {
hello: () => 'world',
// 3. 加上 me 的 resolver (一定要在 Query 中喔)
me: () => users[0]
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({ url }) => {
console.log(`? Server ready at ${url}`);
});
啟動後 (node index.js
or npm start
) 可測試以下 query :
{
me {
id
name
age
}
}
應出現:
{
"data": {
"me": {
"id": "1",
"name": "Fong",
"age": 23
}
}
}
如圖:
Schema + Resolver 是不是很簡單啊~
但不是說 Object Type 裡的 field 也可以使用其他 Object Type 嗎?
沒錯但別急!下一篇會教進階一點的 Resolver: Field Resover
的應用以及 Array 等的深入教學,讓你實作「好友功能」
小問題 1:如果 Schema 是菜單、 Resolver 是廚房,那點餐的你是?
點菜的你是前天講的 Query!...是嗎 ? 嚴格來說,點餐的人是 client 端 (手機或筆電),「點菜的請求」 才是 Query 喔!
小問題 2: 所以 business logic 也是 Resolver 的守備範圍囉?
說是守備範圍只算半對,基於 Facebook 官方的建議,較好的做法是 delegate to a dedicated businesslogic layer ,也就是轉交給專門的商業邏輯層。一方面乾濕分離、一方面也維持商業邏輯程式上的唯一正統性 (single source of truth)以利 reuse。可參考1 2 3
小問題3: 所以
Root Type
算是一種 Object Type 嗎 ?
是的。 Root Type 是一個最多擁有三個 field 的 Object Type,其中三個 field 對應到三個 Object Type (Query, Mutation, Subscription)。你甚至也可以說整個 GraphQL Schema 就是一個 Object Type!
備註:在 GraphQL Schema Definition 中,
可以用單行 "
或是多行 """
來加入文件註釋 (會在文件中呈現),
另外也可以用單行的 #
來表達單純的註釋 (不會在文件中呈現)。
習慣上 type definition 使用 """
來多行註釋,
field 則是使用 "
來單行註釋。
因為這是練習,所以不一定要打文件註釋,但如果今天是正式開發一個產品,那麼即使專案再趕再急也建議加上文件註釋。避免累積技術債絕對能拯救到未來的你!或是製造更多的工作機會 XD
超棒的系列文,不知道為什麼瀏覽人數不多
留言打氣一下!
謝謝!可能我文筆還要再加油哈哈,有些概念可以講的更清楚簡單!
而且除非有興趣要整個學起來~不然單看幾篇其實不好消化,可以理解觀看數較不多的原因 XD
我有一個好奇!! 安裝好 GraphQL 就可以直接與 SQL Server 直接進行查詢的動作了嗎??
恩...這其實是兩個完全不同的概念喔。
GraphQL 只是一層「API」,本質上就是一個有特別功能的 RESTful API ,也就是說 Resolver 之後還是得靠自己去串接 DB 。
不過也有一些工具如 Prisma 標榜直接幫你做好 GraphQL 連接 DB 的部分,讓你專注開發前端。