本文屬使用Prisma構(gòu)建GraphQL服務(wù)系列机断。
每個GraphQL API的核心:GraphQL schema
schema中的類型定義了API操作
每個GraphQL API的核心是GraphQL schema,它清楚地定義了所有可用的API操作和數(shù)據(jù)類型僧家。schema使用稱為模式定義語言(Schema Define Language,SDL)的專用語法編寫鱼喉。 SDL簡潔易用摊唇。
下面是一個演示如何定義簡單的User
類型的例子左医,該User
有兩個字段,即id
和name
特姐。
type User {
id: ID!
name: String!
}
每個GraphQL schema都有三種特殊的根類型晶丘,稱為查詢(Query
),突變(Mutation
)和訂閱(Subscription
)。這些根類型的字段定義了API接受的操作。
作為示例谎砾,請考慮以下Query和Mutation類型:
type Query {
users: [User!]!
}
type Mutation {
createUser(name: String!): User!
}
此schema定義的GraphQL API將允許執(zhí)行以下兩項操作:
# 查詢用戶列表
query {
users {
id
name
}
}
# 創(chuàng)建新用戶
mutation {
createUser(name: "Sarah") {
id
}
}
查詢(Query
) / 突變(Mutation
)內(nèi)的所有字段及其參數(shù)的集合稱為操作的選擇集(selection set)摔竿。
根字段定義API的入口點(entry-points)
根類型上的字段也稱為根字段(root fields)睬关,并為API提供入口點(entry-points)。這意味著發(fā)送到API的查詢(Query
) / 突變(Mutation
)總是需要以其中一個根字段開始。
根字段的類型(type
)確定哪些字段可以進一步包含在查詢的選擇集中。在上面的例子中郁油,類型是User
和[User!]!
在這兩種情況下都允許包含User
類型的任何字段。
如果根字段具有標量(scalar)類型攀痊,則不可能在選擇集中包含任何其他字段桐腌。作為示例,請考慮以下GraphQL schema:
type Query {
hello: String!
}
此查詢定義的GraphQL API只接受一個hello
操作:
query {
hello
}
解析器函數(shù)(Resolver functions)實現(xiàn)schema
GraphQL服務(wù)中的結(jié)構(gòu)與行為
GraphQL具有明確分離的結(jié)構(gòu)(structure)和行為(behaviour)苟径。雖然SDL schema定義僅描述了API的抽象結(jié)構(gòu)(abstract structure)案站,但具體實現(xiàn)是通過所謂的解析器功能(Resolver functions)實現(xiàn)的。schema定義和解析器(resolver)實現(xiàn)的組合通常被稱為可執(zhí)行schema(executable schema)棘街。
GraphQL schema中的每個字段都由一個解析器函數(shù)支持蟆盐,這意味著解析器函數(shù)與GraphQL schema中的字段一樣多(包括除根類型之外的其他類型的字段)。
他為某個領(lǐng)域的解析器功能負責(zé)為該領(lǐng)域準確提取數(shù)據(jù)遭殉。例如石挂,上面的用戶root字段的解析器知道如何獲取用戶列表。
字段的解析器函數(shù)負責(zé)為該字段準確提取數(shù)據(jù)恩沽。例如誊稚,上面的user
根字段的解析器知道如何獲取用戶列表翔始。
因此罗心,GraphQL查詢解析的過程其實是調(diào)用查詢中包含的字段的解析器函數(shù)的操作,因為每個解析器都會為其字段返回數(shù)據(jù)城瞎。
解析器函數(shù)(Resolver functions)剖析
解析器函數(shù)總是需要四個參數(shù)(按以下順序):
-
parent
(有時也稱為root) :查詢由GraphQL引擎解析渤闷,該引擎調(diào)用查詢中包含的字段的解析器。因為查詢可以包含嵌套字段脖镀,所以可能會有多級解析器執(zhí)行飒箭。parent
參數(shù)始終表示前一個解析器調(diào)用的返回值。請參閱此處了解更多信息。 -
args
:為該字段提供的潛在參數(shù)(例如弦蹂,上面的createUser突變示例中的name
)肩碟。 -
context
:上下文,每個解析器都可以寫入和讀取的解析器鏈(基本上是解析器交流和共享信息的方法)的對象凸椿。 -
info
:查詢或變異的AST表示削祈。您可以在這篇文章中了解更多信息:Demystifying the info Argument in GraphQL Resolvers.
以下是我們?nèi)绾螌崿F(xiàn)上述模式定義的解析器的一種可能方式(實現(xiàn)假定有一些全局對象數(shù)據(jù)庫提供了與數(shù)據(jù)庫的接口):
const Query = {
users: (parent, args, context, info) => {
return db.users()
}
}
const Mutation = {
createUser: (parent, args, context, info) => {
return db.createUser(args.name)
}
}
const User = {
id: (parent, args, context, info) => parent.id,
name: (parent, args, context, info) => parent.name,
}
上面的示例schema定義恰好具有四個字段。此解析器實現(xiàn)提供了四個相應(yīng)的解析器功能脑漫。請注意髓抑,User
類型的解析器實際上可以省略,因為它們的實現(xiàn)是微不足道的优幸,并且由GraphQL執(zhí)行引擎推斷吨拍。
Prisma如何幫助開發(fā)GraphQL服務(wù)
構(gòu)建GraphQL服務(wù)的難點在于實現(xiàn)解析器(resolvers)
如上所見,實現(xiàn)GraphQL服務(wù)器的主要開發(fā)任務(wù)圍繞著定義schema并實現(xiàn)相應(yīng)的解析器(resolver)功能网杆。這也被稱為schema驅(qū)動開發(fā)(schema-driven development羹饰,SDD)。
當(dāng)實現(xiàn)解析器功能時碳却,您需要選擇某種數(shù)據(jù)源严里,為查詢響應(yīng)部分時獲取的數(shù)據(jù)。這個數(shù)據(jù)源可以是任何東西 - 它可能是一個數(shù)據(jù)庫(SQL或NoSQL)追城,一個REST API刹碾,一些第三方服務(wù)或任何類型的遺留系統(tǒng)。
上面的例子很簡單座柱,并假設(shè)全局數(shù)據(jù)庫對象的可用性為一些數(shù)據(jù)源提供了一個簡單的接口迷帜。實際上,您可能會遇到更復(fù)雜的場景色洞。特別是因為GraphQL查詢可以深度嵌套戏锹,將它們轉(zhuǎn)換為SQL(或其他數(shù)據(jù)庫API)非常麻煩并且容易出錯。
Prisma讓解析器變得簡單明了
使用Prisma時火诸,一般認為解析器只是將傳入查詢的執(zhí)行委托給底層的Prisma引擎锦针,而不是直接訪問數(shù)據(jù)庫。因此置蜀,大多數(shù)解析器實現(xiàn)將是簡單的單線程奈搜。將傳入查詢解釋為數(shù)據(jù)庫API的工作由Prisma完成。
使用GraphQL bindings搞定這個事情盯荤,允許通過調(diào)用JavaScript中的專用函數(shù)(或任何其他用于后端開發(fā)的編程語言)與Prisma GraphQL API交互馋吗。如此,解析器實現(xiàn)變得與上面的模擬db
示例一樣簡單秋秤。
GraphQL bindings - 更好的 ORM
綁定允許通過調(diào)用編程語言中的函數(shù)來發(fā)送查詢和突變
GraphQL bindings宏粤,在某種程度上與傳統(tǒng)的ORM有點像脚翘。他們都允許通過調(diào)用編程語言中的函數(shù)來與GraphQL API通訊,而不是構(gòu)建直接發(fā)送給API的原始查詢字符串绍哎。
像上面的createUser
突變来农。無論何時您想將其發(fā)送到GraphQL API,您都需要像這樣拼出整個突變:
mutation {
createUser(name: "Sarah") {
id
}
}
然后你將這個字符串放入HTTP POST請求的body
并將它發(fā)送到API崇堰,這樣的缺點是查詢被表示為字符串备图。這抹殺了GraphQL的核心優(yōu)勢之一:強大的類型系統(tǒng)!基于字符串的方法完全沒有發(fā)揮強類型API的優(yōu)勢赶袄。
GraphQL bindings通過允許您調(diào)用專用函數(shù)來向API發(fā)送查詢和突變揽涮,而不是通過手動構(gòu)建字符串并通過HTTP將其發(fā)送到服務(wù)端。這些函數(shù)以您的GraphQL schema的根字段命名饿肺。
通過綁定蒋困,上面的createUser
突變可以通過調(diào)用相同名稱的函數(shù)發(fā)送到服務(wù)端:
binding.mutation.createUser({ name: "Sarah" }, '{ id }')
同樣,你可以將上面的users
查詢轉(zhuǎn)換為函數(shù)調(diào)用:
binding.query.users({}, '{ id name }')
如你所見敬辣,這些函數(shù)調(diào)用中的第一個參數(shù)是一個攜帶查詢(query)/突變(mutation)參數(shù)(args)的對象雪标,第二個參數(shù)是決定哪些數(shù)據(jù)應(yīng)該包含在響應(yīng)中的選擇集(selection set)。
調(diào)用這些方法時溉跃,引擎中的binding
實例負責(zé)將操作轉(zhuǎn)換為GraphQL查詢村刨,并將查詢發(fā)送到服務(wù)端,并將響應(yīng)作為編程語言中的對象返回撰茎。
靜態(tài)與動態(tài)綁定(Static vs Dynamic Bindings)
綁定可以使用兩種風(fēng)格:靜態(tài)(Static)和動態(tài)(Dynamic)嵌牺。
靜態(tài)綁定(Static bindings )用于與來自靜態(tài)和強類型編程語言(如TypeScript或Scala)的GraphQL API進行交互時,在編譯時需要知道所有表達式的類型龄糊。在這種情況下逆粹,綁定函數(shù)在構(gòu)建時生成(使用代碼生成)。因此炫惩,這些綁定函數(shù)的所有調(diào)用都可以通過編譯器進行驗證僻弹,并且拼寫錯誤以及結(jié)構(gòu)錯誤(如錯誤類型的傳遞參數(shù))在編譯時被捕獲。
另一個優(yōu)勢是您的編輯器現(xiàn)在可以幫助您創(chuàng)建API請求他嚷,例如自動完成可用的操作和查詢參數(shù)蹋绽!這樣就改變了后端開發(fā)游戲的玩法,并將開發(fā)者體驗提升到一個新的水平筋蓖。沒有SQL字符串或其他脆弱的數(shù)據(jù)庫API - 幸虧有了Prisma綁定使你可以用強類型層與數(shù)據(jù)庫進行交互卸耘!
動態(tài)綁定(Dynamic bindings )通常用于動態(tài)編程語言(如JavaScript)。它們不需要額外的構(gòu)建步驟(靜態(tài)綁定就是如此)扭勉。綁定實例上的方法調(diào)用僅在運行時轉(zhuǎn)換為GraphQL查詢鹊奖。這仍然提供了使用簡潔和簡單的綁定語法的主要好處。使用適當(dāng)?shù)臉?gòu)建工具仍然可以實現(xiàn)構(gòu)建時錯誤檢查和自動完成等優(yōu)勢涂炎。
架構(gòu)入門:兩個GraphQL API層
在使用Prisma構(gòu)建GraphQL服務(wù)器時忠聚,您需要處理兩個GraphQL API:
- database layer:由Prisma負責(zé)的數(shù)據(jù)庫層
- application layer:應(yīng)用層負責(zé)與寫入或讀取數(shù)據(jù)庫數(shù)據(jù)無關(guān)的任何功能(如業(yè)務(wù)邏輯,身份驗證和權(quán)限唱捣,第三方集成等)两蟀。
數(shù)據(jù)庫層完全通過prisma.yml
進行配置,并使用Prisma CLI進行管理震缭。應(yīng)用層是您正在用自己喜歡的編程語言實現(xiàn)的GraphQL服務(wù)赂毯。