【譯】GraphQL Server基礎(chǔ)(一):Schema电媳,TypeDefs和Resolvers解釋

GraphQL服務(wù)器的結(jié)構(gòu)和實(shí)現(xiàn)(第一部分)

英文原址 該序列文章其實(shí)我覺得只有第三篇有些用處前兩篇鳞上、內(nèi)容相對比較尷尬(這第一篇只是順手翻譯過來)攀痊。如果你比較趕時間或者初學(xué)者其實(shí)本篇我覺得不太適合藐不,感興趣可以直接看第二(英文)第三(中文)

在開始使用GraphQL時抡砂,首先要問的問題之一是如何構(gòu)建GraphQL服務(wù)器贞奋?facebook已經(jīng)已經(jīng)簡單地發(fā)布了一個GraphQL規(guī)范,GraphQL服務(wù)器理論上可以使用任何您喜歡的編程語言實(shí)現(xiàn)咆霜。

在開始構(gòu)建服務(wù)器之前邓馒,GraphQL要求您設(shè)計一個 Schema,該Schema定義服務(wù)器的的API蛾坯。在這篇文章中光酣,我們希望了解Schema的主要組件,闡明實(shí)際實(shí)現(xiàn)它的機(jī)制脉课,并了解如何使用GraphQL.js這樣的庫救军,graphql-toolsgraphene-js在此過程中為您提供幫助。

本文僅涉及簡單的GraphQL功能 - 沒有定義服務(wù)器如何與客戶端通信的網(wǎng)絡(luò)層概念倘零。重點(diǎn)是“GraphQL執(zhí)行引擎”和查詢解析過程的內(nèi)部工作原理唱遭。要了解網(wǎng)絡(luò)層,請查看下一篇文章呈驶。

GraphQL Schema 定義服務(wù)器的API

Defining schemas:模式定義語言

GraphQL有自己的類型語言拷泽, GraphQL Schema:Schema Definition Language
(SDL)。在最簡單的形式中袖瞻,GraphQL SDL可用于定義如下所示的類型:

type User {
  id: ID!
  name: String
}

User單獨(dú)的類型不會向客戶端應(yīng)用程序公開任何功能司致,它只是在應(yīng)用程序中定義用戶模型的結(jié)構(gòu)。為了增加功能的API虏辫,你需要字段添加到根類型的GraphQL架構(gòu):Query蚌吸,MutationSubscription。這些類型定義了GraphQL API 的入口點(diǎn)砌庄。

例如羹唠,請考慮以下查詢:

query {
  user(id: "abc") {
    id
    name
  }
}

僅當(dāng)相應(yīng)的GraphQL架構(gòu)Query使用以下user字段定義根類型時,此查詢才有效:

type Query {
  user(id: ID!): User
}

因此娄昆,Schema的根類型決定了服務(wù)器可以接受的Query佩微,Mutation的形狀。

GraphQL架構(gòu)為客戶端 - 服務(wù)器通信提供了明確的契約萌焰。

GraphQL Schema對象是GraphQL服務(wù)器的核心

GraphQL.js是Facebook的GraphQL參考實(shí)現(xiàn)哺眯,為其他庫提供了基礎(chǔ),比如graphql-toolsgraphene-js扒俯。使用這些庫中的任何一個時奶卓,您的開發(fā)過程都以一個GraphQL Schema對象為中心,包括兩個主要組件:

  • Schema 定義
  • 解析器函數(shù)形式實(shí)現(xiàn)

對于上面的示例撼玄,該GraphQLSchema對象如下所示:

const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: GraphQLID },
    name: { type: GraphQLString }
  }
})

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      user: {
        type: UserType,
        args: {
          id: { type: GraphQLID }
        }
      }
    }
  })
})

如您所見夺姑,Schema的SDL版本可以直接轉(zhuǎn)換為類型的JavaScript表示形式GraphQL Schema。請注意掌猛,此Schema沒有任何解析器 - 因此不允許您實(shí)際執(zhí)行任何Query盏浙,Mutation。更多關(guān)于下一節(jié)的內(nèi)容。

解析器實(shí)現(xiàn)API

GraphQL服務(wù)器中的結(jié)構(gòu)與行為

GraphQL具有明確的結(jié)構(gòu)行為分離废膘。GraphQL服務(wù)器的結(jié)構(gòu) - 正如我們剛剛討論的那樣 - 是它的Schema竹海,是服務(wù)器功能的抽象描述。這個結(jié)構(gòu)通過一個確定服務(wù)器行為的具體實(shí)現(xiàn)實(shí)現(xiàn)丐黄。實(shí)現(xiàn)的關(guān)鍵組件是所謂的解析器功能斋配。

GraphQL架構(gòu)中的每個字段都由解析程序支持。

在最基本的形式中灌闺,GraphQL服務(wù)器在其模式中的每個字段都有一個解析器函數(shù)许起。每個解析器都知道如何獲取其字段的數(shù)據(jù)。由于GraphQL查詢本質(zhì)上只是一個字段集合菩鲜,因此為了收集所請求的數(shù)據(jù)园细,實(shí)際需要做的所有GraphQL服務(wù)器都會調(diào)用查詢中指定的字段的所有解析器函數(shù)。(這也是為什么GraphQL經(jīng)常與RPC風(fēng)格的系統(tǒng)進(jìn)行比較的原因接校,因?yàn)樗举|(zhì)上是一種用于調(diào)用遠(yuǎn)程函數(shù)的語言猛频。)

解析器解析器功能

使用GraphQL.js時,GraphQL Schema對象中某個類型的每個字段都可以resolve附加一個函數(shù)蛛勉。讓我們從上面考慮我們的例子鹿寻,特別userQuery類型上的字段- 這里我們可以添加一個簡單的resolve函數(shù)如下:

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      user: {
        type: UserType,
        args: {
          id: { type: GraphQLID }
        },
        resolve: (root, args, context, info) => {
          const { id } = args      // the `id` argument for this field is declared above
          return fetchUserById(id) // hit the database
        }
      }
    }
  })
})

假設(shè)一個函數(shù)fetchUserById實(shí)際可用并返回一個User實(shí)例(帶有idname字段的JS對象),該resolve函數(shù)現(xiàn)在可以執(zhí)行Schema诽凌。

在我們深入研究之前毡熏,讓我們花一點(diǎn)時間來理解傳遞給解析器的四個參數(shù):

  1. root(有時也稱為parent):還記得我們?nèi)绾握f一個GraphQL服務(wù)器需要做的就是解析一個查詢是調(diào)用查詢字段的解析器嗎?好吧侣诵,它正在做廣度優(yōu)先(逐級)并且root每個解析器調(diào)用中的參數(shù)只是前一次調(diào)用的結(jié)果(如果沒有另外指定則初始值為null)痢法。
  2. args:該參數(shù)攜帶用于查詢的參數(shù),在這種情況下idUser要提取杜顺。
  3. context:一個通過解析器鏈傳遞的對象财搁,每個解析器可以寫入和讀取(基本上是解析器通信和共享信息的方法)躬络。
  4. info:查詢或變異的AST表示尖奔。您可以在本系列的第III部分中閱讀有關(guān)詳細(xì)信息的更多信息:揭開GraphQL解析器中的信息參數(shù)的神秘面紗

之前我們曾聲明GraphQL架構(gòu)中的每個字段都由解析器函數(shù)支持∏畹保現(xiàn)在我們只有一個解析器提茁,而我們的模式總共有三個字段:Query類型上的根字段用戶,以及類型上的idname字段User馁菜。其余兩個領(lǐng)域仍然需要他們的解析器茴扁。正如您將看到的,這些解析器的實(shí)現(xiàn)是微不足道的:

const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { 
      type: GraphQLID,
      resolve: (root, args, context, info) => {
        return root.id
      }
    },
    name: { 
      type: GraphQLString,
      resolve: (root, args, context, info) => {
        return root.name
      }
    }
  }
})

查詢執(zhí)行

注:根(域)指Query火邓,Mutation和Subscription

考慮到我們上面的查詢丹弱,讓我們了解它是如何執(zhí)行的以及如何收集數(shù)據(jù)。在總查詢中包含三個字段:user(在根字段)铲咨,idname躲胳。這意味著當(dāng)查詢到達(dá)服務(wù)器時,服務(wù)器需要調(diào)用三個解析器函數(shù) - 每個字段一個纤勒。讓我們來看看執(zhí)行流程:

  1. 查詢到達(dá)服務(wù)器坯苹。
  2. 服務(wù)器為根域調(diào)用解析器user- 讓我們假設(shè)fetchUserById返回此對象:{ "id": "abc", "name": "Sarah" }
  3. 服務(wù)器為類型id上的字段調(diào)用解析程序Userroot此解析程序的輸入?yún)?shù)是前一次調(diào)用的返回值摇天,因此它可以簡單地返回root.id粹湃。
  4. 類似于3,但最后返回root.name泉坐。(注意3和4可以并行發(fā)生为鳄。)
  5. 解決過程終止 - 最后結(jié)果包含一個data字段以符合GraphQL規(guī)范
{
  "data": {
    "user": {
      "id": "abc",
      "name": "Sarah"
    }
  }
}

現(xiàn)在,你真的需要寫解析器為user.iduser.name自己呢腕让?使用GraphQL.js時孤钦,如果實(shí)現(xiàn)與示例中一樣簡單,則不必實(shí)現(xiàn)解析程序纯丸。因此偏形,您可以省略它們的實(shí)現(xiàn),因?yàn)镚raphQL.js已經(jīng)根據(jù)字段名稱和根參數(shù)推斷出它需要返回的內(nèi)容觉鼻。

優(yōu)化請求:DataLoader模式

使用上述執(zhí)行方法俊扭,當(dāng)客戶端發(fā)送深層嵌套查詢時,很容易遇到性能問題坠陈。假設(shè)我們的API也有包含要求和允許此查詢的注釋的文章

query {
  user(id: "abc") {
    name
    article(title: "GraphQL is great") {
      comments {
        text
        writtenBy {
          name
        }
      }
    }
  }
}

請注意萨惑,我們?nèi)绾我筇囟?code>article的給予user,以及其commentsname誰寫它們的用戶號第

我們假設(shè)這篇文章有五條評論仇矾,都是由同一個用戶編寫的咒钟。這意味著我們會擊中writtenBy解析器五次,但每次都會返回相同的數(shù)據(jù)若未。該的DataLoader讓你在這些種情況朱嘴,以避免N + 1查詢問題優(yōu)化-總的想法是,解析器調(diào)用批處理粗合,因此數(shù)據(jù)庫(或其他數(shù)據(jù)源)只需要進(jìn)行一次打擊萍嬉。

要了解有關(guān)DataLoader的更多信息,您可以觀看Lee Byron 撰寫的精彩視頻:DataLoader - 源代碼演練(約35分鐘)

GraphQL.js vs. graphql-tools

現(xiàn)在讓我們來談?wù)効捎玫膸煜毒危鼈兛梢詭椭贘avaScript中實(shí)現(xiàn)GraphQL服務(wù)器 - 這主要是關(guān)于GraphQL.js和graphql-tools壤追。之間的區(qū)別。

GraphQL.js為graphql-tools提供了基礎(chǔ)

要理解的第一個關(guān)鍵事項(xiàng)是GraphQL.js提供了基礎(chǔ)graphql-tools供屉。它通過定義所需類型行冰,實(shí)現(xiàn)模式構(gòu)建以及查詢驗(yàn)證和解決方案來完成所有繁重任務(wù)溺蕉。graphql-tools然后在GraphQL.js之上提供一個瘦的便利層。

讓我們快速瀏覽一下GraphQL.js提供的功能悼做。請注意疯特,其功能通常以GraphQLSchema

  • parse并且buildASTSchema:給定在GraphQL SDL中定義為字符串的GraphQL架構(gòu),這兩個函數(shù)將創(chuàng)建一個GraphQLSchema實(shí)例:const schema = buildASTSchema(parse(sdlString))肛走。
  • validate:給定GraphQLSchema實(shí)例和查詢漓雅,validate確保查詢遵循模式定義的API。
  • execute:給定GraphQLSchema實(shí)例和查詢朽色,execute調(diào)用查詢字段的解析器并根據(jù)GraphQL規(guī)范創(chuàng)建響應(yīng)邻吞。當(dāng)然,這只有在解析器是GraphQLSchema實(shí)例的一部分時才有效(否則它只是帶有菜單但沒有廚房的餐廳)葫男。
  • printSchema:獲取一個GraphQLSchema實(shí)例并在SDL中返回其定義(作為字符串)抱冷。

請注意,GraphQL.js中最重要的功能是graphql獲取GraphQLSchema實(shí)例和查詢 - 然后調(diào)用validateexecute

graphql(schema, query).then(result => console.log(result))

要了解所有這些函數(shù)梢褐,請查看這個簡單的節(jié)點(diǎn)腳本徘层,該腳本在一個簡單的示例中使用它們。

graphql函數(shù)正在對一個模式執(zhí)行GraphQL查詢利职,該模式本身已經(jīng)包含結(jié)構(gòu)行為趣效。因此,主要作用graphql是協(xié)調(diào)解析器函數(shù)的調(diào)用猪贪,并根據(jù)提供的查詢的形狀打包響應(yīng)數(shù)據(jù)跷敬。在這方面,該功能實(shí)現(xiàn)的graphql功能也稱為GraphQL引擎热押。

graphql-tools:橋接接口和實(shí)現(xiàn)

使用GraphQL的一個好處是西傀,您可以采用模式優(yōu)先開發(fā)過程,這意味著您構(gòu)建的每個功能首先在GraphQL模式中顯示 - 然后通過相應(yīng)的解析器實(shí)現(xiàn)桶癣。這種方法有許多好處拥褂,例如它允許前端開發(fā)人員在由后端開發(fā)人員實(shí)際實(shí)現(xiàn)之前開始對抗模擬的API - 感謝SDL。

GraphQL.js的最大缺點(diǎn)是它不允許您在SDL中編寫模式牙寞,然后輕松生成a的可執(zhí)行版本GraphQLSchema饺鹃。

如上所述,您可以GraphQLSchema使用parse和從SDL 創(chuàng)建實(shí)例buildASTSchema间雀,但這缺少了resolve使執(zhí)行成為可能的必需功能悔详!GraphQLSchema使用GraphQL.js 創(chuàng)建可執(zhí)行文件的唯一方法是手動將resolve函數(shù)添加到模式的字段中。

graphql-tools用一個重要的功能填補(bǔ)這個空白:addResolveFunctionsToSchema惹挟。這非常有用茄螃,因?yàn)樗捎糜谔峁└玫幕赟DL的API來創(chuàng)建模式。而這恰恰graphql-tools與以下內(nèi)容有關(guān)makeExecutableSchema

const { makeExecutableSchema } = require('graphql-tools')

const typeDefs = `
type Query {
  user(id: ID!): User
}
type User {
  id: ID!
  name: String
}`

const resolvers = {
  Query: {
    user: (root, args, context, info) => {
      return fetchUserById(args.id)
    }
  },
}

const schema = makeExecutableSchema({
  typeDefs,
  resolvers
})

因此连锯,使用graphql-tools它的最大好處是它可以很好地連接聲明性模式和解析器归苍!

什么時候不用graphql-tools用狱?

我們剛剛了解到,graphql-tools它的核心在GraphQL.js之上提供了一個便利層拼弃,那么在它不是實(shí)現(xiàn)服務(wù)器的正確選擇時會出現(xiàn)這種情況嗎夏伊?

與大多數(shù)抽象一樣,graphql-tools通過犧牲其他地方的靈活性肴敛,可以使某些工作流更容易。它提供了一個驚人的“入門” - 經(jīng)驗(yàn)吗购,并避免摩擦医男,快速建立一個GraphQLSchema。如果你的后端有更多的自定義要求捻勉,比如動態(tài)構(gòu)建和修改你的模式镀梭,它的緊身胸衣可能有點(diǎn)太緊 - 在這種情況下你可以回到使用GraphQL.js。

快速說明 graphene-js

graphene-js是一個新的GraphQL庫踱启,遵循Python對應(yīng)的想法报账。它還使用了GraphQL.js,但不允許SDL中的模式聲明埠偿。

graphene-js深深地接受現(xiàn)代JavaScript語法透罢,提供直觀的API,其中查詢和突變可以作為JavaScript類實(shí)現(xiàn)冠蒋∮鹌裕看到更多的GraphQL實(shí)現(xiàn)以新鮮的想法豐富生態(tài)系統(tǒng),這是非常令人興奮的抖剿!

結(jié)論

在本文中朽寞,我們揭示了GraphQL執(zhí)行引擎的機(jī)制和內(nèi)部工作原理。從GraphQL模式開始斩郎,該模式定義服務(wù)器的API并確定將接受哪些查詢和突變脑融,以及響應(yīng)格式的外觀。然后缩宜,我們深入研究了解析器函數(shù)肘迎,并概述了GraphQL引擎在解析傳入查詢時啟用的執(zhí)行模型。最后概述了可用于幫助您實(shí)現(xiàn)GraphQL服務(wù)器的JavaScript庫锻煌。

如果您想要實(shí)際了解本文中討論的內(nèi)容膜宋,請查看存儲庫。請注意炼幔,它有一個graphql-jsgraphql-tools分支來比較不同的方法秋茫。

通常,重要的是要注意GraphQL.js提供了構(gòu)建GraphQL服務(wù)器所需的所有功能 - graphql-tools只需在頂部實(shí)現(xiàn)一個便利層乃秀,以滿足大多數(shù)用例并提供一個很棒的“入門”體驗(yàn)肛著。只有在構(gòu)建GraphQL架構(gòu)的更高級要求時圆兵,才可以關(guān)閉手套并使用簡單的GraphQL.js。

接下來的文章中枢贿,我們將討論實(shí)施GraphQL服務(wù)器殉农,比如網(wǎng)絡(luò)層和不同的庫express-graphql, apollo-server
graphql-yoga
。然后局荚,第3部分介紹了GraphQL解析器中info對象的結(jié)構(gòu)和作用超凳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耀态,隨后出現(xiàn)的幾起案子轮傍,更是在濱河造成了極大的恐慌,老刑警劉巖首装,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件创夜,死亡現(xiàn)場離奇詭異,居然都是意外死亡仙逻,警方通過查閱死者的電腦和手機(jī)驰吓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來系奉,“玉大人檬贰,你說我怎么就攤上這事∪绷粒” “怎么了偎蘸?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞬内。 經(jīng)常有香客問我迷雪,道長,這世上最難降的妖魔是什么虫蝶? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任章咧,我火速辦了婚禮,結(jié)果婚禮上能真,老公的妹妹穿的比我還像新娘赁严。我一直安慰自己,他們只是感情好粉铐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布疼约。 她就那樣靜靜地躺著,像睡著了一般蝙泼。 火紅的嫁衣襯著肌膚如雪程剥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天汤踏,我揣著相機(jī)與錄音织鲸,去河邊找鬼舔腾。 笑死,一個胖子當(dāng)著我的面吹牛搂擦,可吹牛的內(nèi)容都是我干的稳诚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瀑踢,長吁一口氣:“原來是場噩夢啊……” “哼扳还!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橱夭,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氨距,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徘钥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衔蹲,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肢娘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年呈础,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橱健。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡而钞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拘荡,到底是詐尸還是另有隱情臼节,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布珊皿,位于F島的核電站网缝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蟋定。R本人自食惡果不足惜粉臊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驶兜。 院中可真熱鬧扼仲,春花似錦、人聲如沸抄淑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肆资。三九已至矗愧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郑原,已是汗流浹背贱枣。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工监署, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纽哥。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓钠乏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親春塌。 傳聞我的和親對象是個殘疾皇子晓避,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354