0%

GraphQL Schema 設計 (2)

變動 Mutation

mutation 必須在 schema 中定義。與 query 一樣,我們要在 schema 內,用 mutation 自訂的物件型態定義它。技術上,在 schema 中定義 mutation 與 query 的方式沒有任何差異。有差異的是它們的目的。你只需要在動作或事件會改變有關於 app 的狀態時建立 mutation。

mutation 代表 app 的動詞,它們只應該包含使用者可以用你的服務做的事情。當你設計 GraphQL 服務時,可列出使用者可以用你的 app 做的所有動作,它們可能都是你的 mutation。

在 PhotoShape app 中,使用者可以登入、貼出照片與標記照片。這些動作都會改變一些 app 的狀態。當使用者登入後,用戶端的使用者就會改變。當使用者貼出照片時,系統會多出一張照片。有人標記照片時也會改變狀態,每當有照片被標記時,就會產生新的照片標記資料紀錄。

我們可以將這些 mutation 加入 schema 內的根 mutation 型態,讓用戶端可以使用他們。我們從第一個 mutation postPhoto 開始寫起。

1
2
3
4
5
6
7
8
9
10
11
12
type Mutation {
postPhoto(
name: String!
description: String
category: PhotoCategory=PORTRAIT
): Photo!
}

schema {
query: Query
mutation: Mutation
}

在 Mutation 型態下面加入一個稱為 postPhoto 的欄位可讓使用者貼出照片。目前只是詮釋這個動作,還需要 GraphQL 服務的實作。

當使用者貼出照片時,至少需要提供照片的 name,而 description 與 category 是可選的。如果使用者沒有提供 category 引數,貼出的照片將會使用預設值 PROTRAIT。例如,使用者可以傳送下列的 mutation 來貼出照片:

client side
1
2
3
4
5
6
7
8
9
10
mutation {
postPhoto(name: "Sending the Palisades") {
id
url
created
postedBy {
name
}
}
}

使用者可以在貼出照片後選擇關於剛才貼出的照片的資訊。這是很好的功能,因為有些新照片資料是在伺服器上產生的,例如新照片的 ID 是資料庫建立的,照片的 url 是自動生成的,我們也會幫照片加上它被 created 建立時的日期與時間時戳。照片被貼出之後,query 可以選擇以上所有新欄位。

此外,選擇組也有關於貼出照片的使用者資訊。使用者必須登入才能貼出照片。如果目前沒有登入的使用者,這個 mutation 就要回傳錯誤。如果有使用者登入了,我們可以透過 postedBy 欄位來取得關於誰貼出照片的資料。

輸入型態 Input

你或許已經發現,有一些 query 與 mutation 的引數愈來愈長了。有一種調整這些引數的好方法,使用輸入型態。輸入型態類似 GraphQL 物件型態,但他只供輸入引數使用。

1
2
3
4
5
6
7
8
9
input PostPhotoInput{
name: String!
description: String!
category: PhotoCategory=POSTRAIT
}

type Mutation {
postPhoto(input: PostPhotoInput!): Photo!
}

PostPhotoInput 型態就像物件型態,但是只供輸入引數使用。這裡 name 與 description 是必須的,但 category 欄位仍然是選用的。現在當你傳送 postPhoto mutation 時,要將新照片的相關資料放在一個物件裡面:

mutation
1
2
3
4
5
6
7
mutation newPhoto($input: PostPhotoInput!) {
postPhoto(input: $input) {
id
url
created
}
}

我們建立這個 mutation 時,將 $input 查詢變數的型態設成 PostPhotoInput! 輸入型態。它不可為 null,因為我們至少要用 input.name 欄位來加入新照片。當我們傳送這個 mutation 時,必須用 input 欄位內的查詢變數(Query Variables)來提供新照片的資料:

1
2
3
4
5
6
7
{
"input": {
"name": "Hanging at the Arc",
"description": "Sunny on the deck of the Arc",
"category": "LANDSCAPE"
}
}

我們的輸入被一起放在一個 JSON 物件裡面,並且在 “input” 鍵底下以 query 變數與 mutation 一起送出。因為查詢變數被格式化為 JSON,category 必須是個符合 PhotoCategory 的其中一種分類的字串。

輸入型態是建構與編寫簡明的 GraphQL schema 的關鍵元素。你可以將輸入型態當成任何欄位的引數,也可以在 app 中用它們來改善資料分頁與資料過濾。

回傳型態

現在 schema 的所有欄位都回傳主要的型態,User 與 Photo。但除了實際的承載資料 (payload data) 之外,有時我們也需要回傳關於 query 與 mutation 的詮釋資訊。例如,如果有使用者已經登入並且通過驗證了,除了 User 承載資料之外,我們也需要回傳權杖 (token)。

1
2
3
4
5
6
7
8
9
type AuthPayload {
user: User!
token: String!
}

type Mutation {
...
adAuth(code: String!): AuthPayload!
}

我們藉由傳送有效的碼給 adAuth mutation 來驗證使用者,成功後,回傳一個自訂的物件型態,裡面有成功登入的使用者的資訊,可用來做進一步授權的權杖,以及包括 postPhoto mutation 的多個 mutation 時所需的使用者資訊。

你可以在任何 “需要除了承載資料之外的資料” 的欄位使用自訂回傳型態。或許除了尋承載資料之外,我們也想要知道一個 query 需要多少時間來傳遞回應,或是在某個回應中可找到多少結果。你可以使用自訂的回傳型態來處理這類的事情。

訂閱 Subscription

Subscription 型態與 GraphQL schema 定義語言的任何其他物件型態沒有甚麼不同。我們要在自訂物件型態內將 subscription 定義成欄位。而當建立 GraphQL 服務時,要自行確保 subscription 實作了 PubSub 設計模式以及一種即時傳輸。

例如,我們可以加入 subscription 來讓用戶端監聽 Photo 或 User 型態的建立:

1
2
3
4
5
6
7
8
9
10
type Subscription {
newPhoto: Photo!
newUser: User!
}

schema {
query: Query
mutation: Mutation
subscription: Subscription
}

我們在這裡建立一個自訂的 Subscription 物件,它有兩個欄位: newPhoto 與 newUser。當使用者貼出新照片時,那張照片會被推送到訂閱 newPhoto subscription 的所有用戶端。有新的使用者被建立時,他們的資料會被推送到每一個監聽新使用者的用戶端。

subscription 與 query 或 mutation 一樣可以使用引數。假如我們要在 newPhoto subscription 加入過濾器,讓它只監聽新的 ACTION 照片:

1
2
3
4
type Subscription {
newPhoto(category: PhotoCategory): Photo!
newUser: User!
}

現在當使用者訂閱 newPhoto subscription 時,他們可以過濾被送到這個 subscription 的照片。例如,若要濾出新的 ACTION 照片,用戶端可傳送下列的操作給 GraphQL API:

query
1
2
3
4
5
6
7
8
9
10
subscription {
newPhoto(category: "ACTION") {
id
name
url
postedBy {
name
}
}
}

這個訂閱只會回傳 ACTION 照片的資料。

如果即時處理資料是很重要的功能,訂閱是很好的解決方案。