乾貨分享 | GraphQL 數據聚合層
- 2019 年 10 月 4 日
- 筆記
先回到會議的主題,為什麼會開這麼一個會議?原本這個會議報名的時候希望可以控制到 100 人之內,不要超過 120 人,結果報到 250 人還爆滿了,後來沒有辦法,我們只能從工作經驗來篩選,選擇工作經驗在三年以上的比較資深的工程師,和一部分 2 年經驗工作經驗的,還有 5 年的,10 年甚至更久的從業者。
那現場我調查一下哈,大家有了解過領域驅動的同學請舉一下手好么,不到十個人,那大家在工作中使用 GraphQL 的同學舉一下手好么,也是十個人不到的樣子,這就是為什麼要辦這個會議。
起因是這樣的,我們當時想要去嘗試 GraphQL 的時候,發現中國的文檔,社區,會議等等關於 GraphQL 的資源都特別少,很匱乏,沒有地方可以去溝通,可以去交流,後來我們只能找自己同行業的朋友去溝通發現大家其實都感覺交流匱乏,我們最後把這條路的坑都踩過了之後,覺得是不是可以自己總結的一些東西單獨拿出來跟大家分享。

在過去不到一年,大概十個月左右的時間裡我們團隊用 GraphQL 實踐了一些產品,發現自己確實從中受益了,覺得說這個東西是可以拿出來跟大家來做分享。

再回到大會的主題,有這麼幾個關鍵詞,一個是協同,一個是效率,領域驅動是單獨的 1 Part,然後還有前後端職能變化。其實這也是我自己感受最深幾個詞,一個技術帶來的價值不僅僅是開發效率本身的提升,可能還會帶來額外的價值,這些額外的價值可能是協同或者是前後端職能的變化。這也是為什麼會去舉辦 GraphQLParty,以及定這個主題的原因,因為無論是 GraphQL 還是領域驅動,包括他們結合後的價值,這個話題在中國目前沒有一家公司真正拿出來去講,我們覺得自己可以第一個來吃這個螃蟹,然後和大家一起去推動這個事情。

今天下午一共有五場分享,有前端後端的分享,也有比較方法論的,當然會有乾貨濕貨,我們能對講師的要求呢,就是不要講太高大上的東西,盡量講比較接地氣的東西。同時我們不知道行業水準怎麼樣,我們會先把自己做的東西,無論水平如何,都拿出來給大家看看。
然後我們這裡有一些問題列了出來,這個在我們開始報名活動開放時候就已經公布出來了。今天下午大家可能會對講師的演講內容產生不同的疑問,你的問題可能是這裡面某一個,也可能不在這個裡面,但沒有關係,如果你想了解 GraphQL,了解領域驅動,可能這裡面可能會有一些問題是你逃不過的。
大家可以帶著問題聽今天下午的五場分享,但能否拿到答案現在也不好說,最終大家一定會有自己的判斷。
當所有問題全部解決掉之後,對我們在場的資深從業者來說可能還會面臨一個終極問題,那就是假如說今天,你們講師們啊,都講的都有道理,我想去用打算去推動,那麼會給我的團隊帶來哪些收益,又會遇到哪些技術上的挑戰,遇到哪些坑。技術挑戰和遇到的坑一會兒由我的搭檔來為大家介紹,我先為大家講一下宋小菜自己有了多大的收益,來舉一些案例。


關於案例呢,先來了解一下我們的產品背景,畢竟技術不能脫離場景,宋小菜 10 個前端工程師,其實不到 10 個,大概是 7 個,是最近幾個月才招到了幾個,我們一共開發和維護了 6 款 APP,1 款小程式,1 個複雜的 ERP 系統,市 調系統,報表系統等等。這看起來像是一個大團隊做的事情,但其實我們最多只有十個前端來負責。那這裡面就會有很多問題,比如說 APP 與 APP 之間,人與人之間的合作,我們之前使用版本不穩定的 RN,踩了很多坑,這個開發和協作成本太高,必須造輪子,或者用輪子優化開發效率,所以針對前端團隊內部做了很多事情,就像 PC 端一樣需要解決資源上線、打包問題、編譯、版本、快取等很多問題,我們也不例外,差不多這些就是 7 個人陸陸續續開發的內部工具,所解決的特定的問題。
舉個例子:大伯伯推包系統,所解決的問題是在我們需要發布 APP 包的時候,如果一個人打的包,釘釘上傳給那個小夥伴,他去上傳的包有可能就會出錯,而且這種故障有發生過,這種場景可能靠人肉是解決不掉的,所以我們開發大伯伯推包系統,讓機器對接機器,通過機器去做這件事情,這裡面有大伯伯,大表姐,大瓜子,為什麼取這個名字,是因為我們希望產品能盡量接地氣,再加上諧音,比如打包叫做大(打)伯(包)伯(包)。
剛才的問題解決掉以後,我們發現還是不行,團隊內部效率配合成本確實降下來了,但是發現團隊之間的成本還降不下來,比如有這三個問題是我們逃不過去的,並且跟前後端關係都很大。

第一個是多端之間的類報表同步,大家現在開發前端,可能開發小程式,APP,PC,會有多端,對我們公司場景來說我們這個端比較雜,可能要在 ERP 要透出報表,在 App 上透出報表,在小程式上透出報表,但是面對不同許可權的人報表透出的維度不一樣,但本質上下面的數據源是同一份,那我們就可能開發很多個介面去對應不同的 APP,那這個問題靠前端是解決不了的。
第二個是多端之間多模組共享,這個模組不太準確,我解釋一下,意思大概是比如有一個用戶模組,一個訂單模組、一個物流模組,每個模組裡面可能會有一兩個組件來組成,不一定是什麼組件,但向下所需要的基本數據可能還是同一份或者同兩份,只是在不同端上的 UI 呈現不一樣。那我們想要讓這些模組之間去共享數據很難,我們在不同端上開發一個組件就綁定一個介面,這個組件拿到另外一個裡面去,套到那個模組去用的時候發現行不通了,這也是一個很大的成本。
第三個是業務變化快,產品總要升級迭代,難免 UI 設計師要找活給你干,要改版的時候,加一個欄位,減一個欄位,或者疊加幾個欄位,那麼介面又要升級,或者加一個新介面,這個同樣導致合作成本很高。

為什麼會這樣?是因為我們都知道啊,基本上行業內通用一個開發流程是這樣的,可能大家的團隊比我圖示上顯示的長一些,短一些都沒有關係,但大家跳不過的是這幾個環節,來看紅色的幾個,系統設計,裡面涉及到 Java 服務端工程怎麼搭建、骨架怎麼搭建、服務怎麼拆分,最後具象的時候,是服務端同學設計這個資料庫和表結構,這幾張表上面的欄位有哪些。然後介面設計,服務端同學會給出介面的文檔,比如說會給你提供五個介面,每個介面 15 個欄位,才會進入到前後端對接完之後,再幫你去做一份 Mock 數據,然後前端在頁面上把頁面樣式重構之後,再去調假的 Mock 數據,然後頁面交互流程調通之後再切到正式介面,大概是這個套路。這裡面有很多工具棧,很多第三方開源的工具可以用,那我們發現這裡面前後端堵塞在這個點,而且堵塞很多年也解決不掉,因為介面設計控制權在服務端手裡,前端不知道會給到多少介面,然後在介面評審時候,15 分鐘或者一個小時,前端基本上很難理解哪個欄位背後的業務含義,過後還要和服務同學反覆再溝通確認,就因為在這個點的堵塞導致上面三個問題不是很好解決。

把三個問題再抽象一下,其實就是這三個:第一個是 API 的設計,服務端同學有的時候會被迫也好,被 「強姦」 也好,我必須要面向你多變的 UI 去做 API 的設計,面向這些頁面服務,頁面變化的時候,API 可能也要升級。第二個是 Mock 職責重合,之前也知道業界很多公司自己做了 Mock 工具和平台,我們一直也是在使用第三方的,有時候是前後端共同維護同一份 Mock 介面,有的時候是服務端去維護,但是總之要存在一個協作成本,到底誰來對它負責,這件事情,到現在也說不清楚。服務端同學給你做完 Mock 之後終於可以沉下心來做底層的業務開發,但發現臨時需要調整一個欄位,就把介面調整了,但是忘記去更新 Mock 文檔,前端不知道這個事情,到後面倆人一對接發現欄位對不上,這就是一個典型的工作流協作問題。還有一個問題,這個對於 toB 公司,或者 toC 也有這樣的場景,就是報表,報表可能是一個剛需,對於管理層其實需要看到過去一周一個月公司交易的整個規模、噸位、物流情況、庫存情況,不同維度的數據觀測。業務打法一變報表的維度也要變,傳統的報表開發就前後端各一個,服務端搞定資料庫,跨表跨庫查詢,給出標準的欄位結構,前端就是把它套到 Table 表格裡面去,這個事情很簡單,但是可能要排期,一天兩天三天,可能產出報表的速度就很有限了。

針對剛才的問題,我們先把宋小菜在自己的業務場景下,技術的解決方案拿出來給大家看一下。我們現在解決方案是在網關這一層,集成 GraphQL 的一個聚合服務,第二場架構師會來講具體架構圖,這邊單獨講這一個點。我們理想中的 GraphQL 接入方式,是跟網關同層嵌入在裡面做一個管道,但是現在我們的實現方式呢,考慮到快速跑通,暫時把它放在網關的下面,是為它的鑒權跟安全不想占太多開發成本,把它交給網關去做了,所以它就只做數據聚合這麼一件事情。

那麼通過系統改造,我們開始回答之前的問題,那就是收益有什麼?2016 年 2017 年差不多 2 年多時間為了整個公司開發的報表一共 50 張報表,總的開發時間沒有詳細計算,但是這並不代表說整個公司只需要看這 50 張就夠了,而是因為我們只有這麼多人力開發這 50 張,報表開發成為一個瓶頸,當我們通過 GraphQL 在端上透出以後,包括服務端去一些拼裝的動作,現在提供了可視化報表編輯的系統,這個系統面向產品經理和運營,面向服務端工程師,他們通過可視化的介面配置一下,報表就生成了。
這個系統上線以後四個月就產出 200 多張報表,把整個公司報表需求全部消化掉了。現在的情況是,產品經理跟業務方開會,業務方說:我需要看一下某個服務站一周的報表數據,我要提需求,我需要這個指標這個指標。然後會還沒開完,產品經理就把報表做完直接上線了,現在報表產出就是這麼一個節奏。通過做這個系統我們發現 GraphQL 可以給我們帶來很大方便,我們就繼續往下挖,把 GraphQL 它的價值從 APP 端繼續往下沉,沉到服務端,我們就做了大舅子,前面的報表系統是大(搭)表(表)哥(格),是搭 Excel 表格的一個諧音,這邊是大舅子,公司的產品啊,他們都是親戚。
大舅子是我們今天分享的主題,剛才那個 GraphQL 的聚合服務叫大舅子,這個到現在為止實踐不到 3 個月時間,跑了一些項目,目前評測下來可以節約的人力是這樣,如果一個小日常小項目需要前端後端共同開發 4 天,我們可以把成本降到 3 天,那這是單個人的狀況,如果人更多的時候,多人對接成本的提升通過這個系統的表現會更加的明顯。

上面是從業務結果拿到的收益,除了這個收益之外還有別的收益。就會涉及到今天另外一個關鍵詞 – 前後端的職能變化。大家心目中的前端跟服務端我不知道是怎麼樣的,我就說下我們現在朝著一個方向走是這樣子的,前端對於頁面上的數據有一定的控制權,我需要什麼樣的數據只有我自己知道,因為我需要對數據有控制權,要更快去輸出頁面,包括去走通一些業務流程,點了什麼按紐,觸發什麼事件,就必須去理解每一個欄位背後的業務含義,我要去理解每個欄位背後的業務含義,就必須去理解業務。以前會說我只需要 UI,理解交互,理解產品就好了,業務我不管,反正給我們什麼欄位就用什麼欄位,我們去消費數據。現在對前端的挑戰在這裡,我要負責的事情有一些變化。對於服務端來說,反而很爽,因為我終於不用再面向多變的頁面去設計 API,我從裡面解放出來了。
那麼解放出來之後會帶來兩個問題:第一個問題是解放出來的時間用來做什麼,第二個問題是如果前端介入到這一層,你們還會對我(服務端)提什麼要求。對於第一個問題,既然有精力時間,我可以把膠水程式碼都拿掉,把 Mock 的時間,粘合數據的時間省下來去做底層的服務設計,提供更穩定的數據服務,反過來前端也會希望說服務端同學提供的介面也好,不同的領域設計也好,給我趨於穩定的設計,而不要給我多變的設計,不要因為每一次前端頁面改版研發而引發後端服務改動的大地震,這是前後端的一個變化。
那我們到底怎麼通過程式碼通過工程的方式拿到這個收益呢,接下來由我的同事陳錦輝跟大家去做具體的工程上的分享,掌聲歡迎陳錦輝。

陳錦輝:大家好,我是宋小菜的前端工程師陳錦輝,剛剛 Scott 給大家講了一下宋小菜在一段時間內實踐了 GraphQL 的結果以及我們搭建基於 GraphQL 的數據聚合系統——大舅子,這是 Scott 的七大姑八大舅系統里的一個成員。這裡簡單介紹一下我要講的主要內容,關於什麼是 GraphQL,我會開始先做一個科普,後面演示一下現在正在試用這麼一個系統,最後有哪些坑需要去踩,最後再針對 GraphQL 開開腦洞。

最開始先看一下大舅子的這個數據聚合系統,在我們整個系統架構裡面到底處於哪一個位置。從圖示中可以看出它處於我們的網關和後端數據服務的中間,剛剛 Scott 也解釋過,我們網關本身已經存在了,所以它已經把如鑒權和安全一類的事情做掉了,所以我們在做數據聚合服務的時候,將這個服務放到網關後面。為什麼取名叫 GPM,它全稱叫 GraphQL Pipe Manager,這是對一個對後端服務提供的數據提供數據拼裝的這麼一個系統。

這是我們整個 GPM 內部架構大概一個結構圖,分成兩部分:一部分是正式的服務,可以看到正式的服務比較簡單,因為要保證提供正式數據服務穩定,所以我們盡量簡化它。另一部分稍微複雜一些的,是開發服務,在開發服務裡面我們可以對類型進行編輯管理,然後在開發服務上進行測試,最後應用到我們的正式數據服務上。

介紹完我們使用 GraphQL 的大致情況後,鑒於在場的部分同學之前可能沒有接觸過 GraphQL,所以我先來介紹一下什麼是 GraphQL,GraphQL 全稱叫 Graph Query Language,官方宣傳語是「為你的 API 量身訂製的查詢語言」,用傳統的方式來解釋就是:相當於將你所有後端 API 組成的集合看成一個資料庫,用戶終端發送一個查詢語句,你的 GraphQL 服務解析這條語句並通過一系列規則從你的「 API 資料庫」裡面將查詢的數據結果返回給終端,而 GraphQL 就相當於這個系統的一個查詢語言,像 SQL 之於 MySQL 一樣。

宋小菜經過一個時間不是很長的實踐,發現使用 GraphQL 給我們帶來五點的比較方便的地方:一個是單一入口,第二個是文檔的展示和編寫,第三個也是比較有特色一點,就是數據冗餘可以使用 GraphQL 來避免,第四點數據聚合是這一個系統本身最主要的職能,還有最後一點就是數據 Mock,Mock 相當於比較棒的附加值。


首先說一下單一的入口這一點。傳統的 RESTful API 里,不管前端還是後端都要對 API 做管理,一是版本管理,二是路徑管理,非常麻煩,增加了工程管理的複雜度。但是如果使用 GraphQL,只需要一個入口就可以了。剛剛也說到 GraphQL 相當於一個資料庫,它的入口只有一個,我們只需要訪問這個入口,將我們要查詢的語句發送給這個入口,就可以拿到相應的數據,所以說它是一個單端點+多樣化查詢方式的這麼一個結構。


第二點是文檔,這裡文檔雖然不能完全替代傳統的文檔,但是它能在一定程度上方便我們。傳統的 RESTful API 文檔管理,市面上有很多工具,像 Swagger、阿里開源的 RAP 以及 showdoc 等。但使用這些 API 文檔管理工具的時候其實是有一定的學習成本的。像 Swagger,可能對於老手來說使用起來不是很複雜,但是對於剛上手的開發者來說上手還是需要一點時間的。然後還有在使用這些平台的時候都會遇到讓人頭痛的「 API 和文檔同步」的問題,很多時候需要自己去做 API 和文檔同步的插件來解決。

如果使用 GraphQL 就可以在一定程度上解決 API 文檔的一些問題:在做 GraphQL 類型定義的時候我們可以對類型以及類型的屬性增加描述 (description) , 這相當於是對類型做注釋,當類型被編譯以後就可以在相應的工具上面看到我們編輯的類型詳情了,像示例的這一個類型 Article,它的描述是 「文章」 ,它的屬性有哪些,有什麼含義,都會展示在大家面前,只要我們在開發的時候規範編寫類型,整個文檔的展示就比較規範了。
使用 GraphQL 還有一個比較棒的功能,就是每一個 GraphQL 類型 其實相當於 mongo 裡面的一個 collection,或者 mongoose 裡面的 Model, 而每一個類型之間關係也可以用工具很形象的表現出來。像系統上用到這麼一個模型,它對應到哪些和它有關係的模型都高亮出來了。


這裡演示一下,可以傳送看一下,在 Github API 4.0 開放出的 GraphQL API,它將 Github 所有的對外類型都暴露出來了。可以看到每一個類型對應的定義和解釋都在左面有顯示出來。每一個類型都有對應的 UML 圖展示,這是一個比較大並且比較複雜的 UML 關係圖。我們主要平時用到一個核心的類型其實就是倉庫 (repository) 類型,我們可以看到這個類型比較複雜,同時它也是比較核心的,和它有所關聯的類型就非常的多,倉庫類型下還有 issue 這個屬性。如果我們參考 Github 開放的 API 4.0,就可以做到在 Github上面開發相關的插件。


使用 GraphQL 的第三點好處就是可以避免數據冗餘。我們在傳統的 RESTful 處理冗餘的數據欄位大約有這麼三種處理方式:
一是前端選擇要不要展示這些欄位;
二是要麼做一個中間層(BFF)去篩選這些欄位,然後再返回終端來展示出來;
三則比較傳統也比較麻煩,還不一定能生效,就是前端和後端去做約定,如果說這一個介面這一個欄位已經不要,可以和後端商量一下把這個刪掉,但是有一種情況可能造成冗餘欄位刪不掉的,那就是後端的同學做這個介面可能是「萬能介面」,也就是說這個介面在這個頁面會用,在另外一個頁面也能用,在這個應用會用,在另外一個應用也可能會用,多端之間存在部分數據共享,後端同學為了方便可能會寫這麼一個「萬能」的介面來應付這種情況,久而久之,發現欄位冗餘到很多了,但是隨便刪除又可能會影響到很多地方,導致這個介面大而不能動,所以前後端都不得不忍受它。
但如果使用 GraphQL,就可以避免介面欄位冗餘這個問題,使用 GraphQL 的話,前端可以自己決定自己想要的返回的數據結構。剛剛我也解釋過,GraphQL 實際上是一種查詢語言,我們在使用時就像是在資料庫裡面查詢數據一樣,查詢的某一個數據要哪些欄位可以在查詢語句里寫好,要哪些欄位就返回給我們哪些欄位。

拿 PPT 上這個作為示例:我們要去拿 id 為 1 的文章,如果我只要 id 和 content,我在 query 裡面指定這兩個欄位,那麼返回的就是 id 和 content,如果除了 id 和 content 之外,我還要拿需要作者資訊的時候,我只需要在 query 裡面指定 author , GraphQL 就將作者的資訊給返回回來。這樣就能做到前端決定自己想要什麼結構的數據返回的就是什麼樣的數據。

最重要一點當然是數據聚合,數據聚合在使用傳統的 RESTful 的方式時有多種解決方案:
一種前端髮針對這個頁面上的多數據源單獨發起數據請求,然後一一展示出來,這樣可能會出現頁面數據載入不同步的情況。
第二種就是開發做數據拼裝的中間層(BFF),用於拼裝後端提供的數據,然後返回給前端。

還有一種是宋小菜在最前期的使用一種方案,那就是後端同學編寫針對頁面的 API,即所謂膠水程式碼,來拼接各個服務的數據,返回給前端。
如果是第三種情況的話,就會有大量的工程需要我們去維護,大量的 API 需要我們去維護。但如果使用 GraphQL 的話,這些問題都不會存在,因為它是天生支援數據拼裝的。

為什麼它是天生支援數據拼裝的呢?我來嘗試著從 GraphQL 執行的原理上大概解釋一下。這是個GraphQL 執行的大致流程,第一步我們去驗證需要去執行 GraphQL 的標準,同時去驗證將要去查詢語句的合法性,第二步生成執行的上下文,關鍵點在第三步和第四步,第三步是獲取查詢語句所需要查詢的欄位,這裡叫 fields,所有需要查詢的欄位可以在查詢語句里通過演算法拿到,這裡可以解釋剛剛提到的 GraphQL 怎麼做到避免返回數據的冗餘的。拿到所有需要查詢的欄位後,第四步針對每一個欄位去執行它的 resolver,可以從 resolver 返回數據裡面拿到欄位對應的數據,最後是格式化結果並返回。


第四步我解釋一下,在GraphQL裡面有一個類型的概念叫類型 (type),每一個類型下面對應的是一個或多個欄位,每一個欄位綁定了一個 resolver,這個 resolver 的作用就是獲取欄位對應的數據。對應到剛剛舉的例子,比如 article 這個類型,它有四個欄位: id,author,content,comment。每一個欄位都對應的一個 resolver。而這個 resolver 其實是可以被開發者重新定義的,如果說沒有定義的話 GraphQL 會給一個默認的 resolver,像Article 的 author 欄位類型是 User , User 可以從用戶服務裡面去獲取,所以我們可以將 author 這個欄位 resolver 重新定義一下,通過 UserService 獲取用戶資訊。下面的評論(comment)也一樣,我們可以通過 CommentService 獲取評論數據,這樣可以做到在查詢這個文章的時候既獲取了文章本身的數據,也通過 UserService 和 CommentService 獲取到了作者資訊和評論資訊,然後經過拼裝返回給客戶端,這樣就達到了使用 GraphQL 進行數據拼接的目的。

第五點是附加的一點,我們可以適當地利用 GraphQL 做數據 mock。那麼使用 GraphQL 怎麼去做到 mock 呢?

GraphQL 的類型大致可以分為兩種類型:
一種標量類型,像普通的開發語言一樣,提供 Int,Float,String 這種標量類型,這種類型在 GraphQL 中也對用著一個 resolver,我們可以通過重新定義其 resolver 來做到對標量類型的 mock, 像 Int 返回的範圍是什麼,Float返回的範圍是什麼?String 返回的格式是什麼樣的?等。同時我們在開發中常用到的一些簡單但是有一定規則的數據類型像手機號碼、圖片地址、身份證號碼、身份證號碼這樣的數據我們也可以通過自定義標量類型來做到數據 mock。
第二種是普通類型,像剛剛示例中的文章(Article)類型,普通類型下面可能會有多個欄位,每個欄位對應的數據類型可能是普通類型也可能是標量類型,這種類型也可以做 mock,如果我們對標量類型做了適當的 mock 以後,像 Article 的 mock 數據就會自動生成。 使用 GraphQL 做數據 mock 還有一個方便之處在於經典 mock 數據可以被很方便地復用。如剛剛示例中查詢 Article 下面類型為 User 的 欄位 author 的時候可以利用這個特性,因為用戶資訊(User)這個類型不僅僅會用於文章的作者也可能會用於評論的作者,所以我們針對User 類型做一個 mock 數據,這個 mock 數據可以會在查詢文章作者和查詢評論作者中同時用到,同時我們也可以在返回 mock 數據時耍一些小花招,例如從幾個用戶數據中隨機返回一個用戶資訊,或者根據查詢條件返回對應的假數據等。

使用 GraphQL 做數據 mock 有多方面的好處:
好處之一就是 mock 數據隨著類型 (type) 走,當我們修改類型以後,它的 mock 數據也是會被同步修改,不會出現 mock 數據和類型不同步的情況;
好處之二就是能很容易地實現 mock 數據的細粒度,原理剛剛也解釋過了,這樣能夠很大提高我們的開發效率。
好處之三是 mock 數據可以復用,節約開發時間。
最後一點,那就是 mock 數據的職責可以由前後端共同承擔。或者說由前端自己來做,因為通常情況下 mock 數據的消費者都是前端自己,為何不自產自銷呢,省去大量的交流成本。
這裡簡單做一個演示,展示一下我們開發出來的還在試用期的一個 BFF 服務—— GPM,目前它的頁面還比較簡陋,畢竟還在還在試用階段嘛。
在 GPM 中每一個生成類型都會以表單的形式展現出來,當然程式碼的形式也會有特定的地方呈現,我們只是對每一個類型都進行可視化,如果作為一個新人來使用,只需要點擊按紐添加類型,指定類型名字,填寫類型描述,根據類型的實際情況設置快取有效時間,綁定到宋小菜的哪些 APP。然後針對已經添加好的類型可以對它做欄位的添加的操作,指定欄位的名字、類型、描述、快取有效時間,以及 mock 數據。
同時這一個系統可以直接在線上測試並發布類型的:我們在編輯好一個類型以後,可以部署到開發環境上,然後在 IDE 裡面做調試提前查看返回數據是否正確。像剛剛說到的處理數據欄位冗餘是怎麼做的,這裡可以演示一下,在前端不想要這一個欄位時,直接在查詢語句裡面刪掉然後執行查詢就能拿到不包含這個欄位的數據了。我們也可以通過 IDE 獲取這個查詢語句結果的 mock 數據。
在寫查詢語句的時候這個 IDE 根據我們已經生產的 schema 自動幫我們提示,就像使用普通的桌面 IDE 一樣,而每一個類型的文檔可以從右邊的彈窗裡面看到。GPM 將 IDE 分為了正式服務和測試服務的 IDE, 正式 IDE 時針對線上數據做查詢的。我們在測試好了新增或者修改的類型以後就可以部署正式環境上了,不用重新發布 GPM 就可以做到。
這是剛剛提到文檔展示,GPM 也集成進來了,可以看到這些類型有哪些,然後這些類型到底有什麼含義,類型和類型之間的關係是什麼樣的,都可以在這裡很方便的去查看。
在 GPM 上我們還做了一些附加的功能,因為我們後端提供的微服務大多數是用使用 RSETful 的方式去調用的,所以我們特意做了一個針對 RSETful 請求的追蹤,這裡可以看到每一個 RSETful 訪問的情況。
最重要的其實是對每一次 GraphQL 查詢語句的追蹤。可以看到,像我們執行這麼一個查詢語句,拿到的數據結果,執行時間,這一個查詢語句的詳情都能看到,同時還可以看到每一個欄位查詢速度如何。又比如說,像這一個介面,它綁定兩個服務,一個服務是囤貨單的服務,還有一個服務是供應商資訊服務,這樣子可以看到每一個查詢欄位它追蹤到這種執行效率怎麼樣的,可以根據這個查詢結果來告知後端同學做優化。

這就是整個 GPM 大概的樣子。因為時間的關係,我就只稍微說一下我們是怎麼去實現在線編輯部署 GraphQL 的。

GPM 是使用 nodejs 搭建的,所以這個方案是針對 nodejs 的,其他語言的解決方案需要大家自己去探索了。實現這個功能有以下幾個關鍵點。
關鍵點之一是替換 schema,實際上 schema 可以被修改的,只要我們使用特定的方式將每次執行的 schema 修改掉,那就做到了每次執行 graphql 時都會使用到最新的 schema 了。
關鍵點之二怎麼做到修改已經在使用中的 schema:我們將 GraphQL 的 schema 分為兩部分:一部分是類型定義,另一部分是 resolver。前面也提到過,每個類型下面有欄位,每個欄位下面綁定了 resolver,我們其實可以把類型定義和 resolver 分開來,同時對 resolver 進行適當的分層。GPM 的分層結構是這樣子,但這是我們自己的這種分層,其實還有其他方案,後面的講師會講到。 然後我們將 resolver 和 type 定義做好以後,將它使用一些開發工具將它綁定起來,就生成了這麼 GraphQL 的 schema 。


在做查詢時候就參考 schema 來做,type 定義本質上是 string,關鍵的一點就是怎麼去動態生成 resolver,也就是第三個關鍵點,這裡稍微簡單講一下。

我們首先需要去簡化 resolver,resolver 本身它的形式是固定的,函數簽名其實就是這樣,類型下面欄位名字,欄位名字下面有四個參數,然後返回結果。第一個參數是父類型的查詢結果,我們有可能會使用到它類型下面的一些查詢的數據;第二個是指定的查詢參數;第三個最就是我們剛剛提到的執行上下文(Context),我們可以在執行上下文 (Context) 裡面去調用綁定的各種服務。這就是是 GPM 中 resolver 大致形式,第一步拼裝參數,第二步使用執行上下文調用服務,就可以動態拿到數據,這樣就可以做到動態生成 resolver。

我們在使用 GraphQL 的時候有一些無法避免的問題是需要去解決的,這裡有兩個繞不開的問題:
第一是安全問題
第二是慢查詢的問題
也許還有其他我們沒發現的需要解決的問題
關於安全的問題後面的講師也會講,時間的關係,這裡就不細講了。

慢查詢在終端已經有很多用快取去解決這個問題的方案了,像 apollo、relay。還有就是在 GraphQL 服務裡面去做快取,apollo 提供的 apollo-engine 就是這種方式,但這個要翻牆才能用,所以只能用來它作為參考。還有一種方案是宋小菜在 GPM 裡面用到的:合理地利用 GraphQL 提供的指令 (directives) ,去置換 resolver,這樣做到 GQ 服務數據的快取。還有使用 dataloader 來批量處理多次重複查詢,後面的講師也會提到。


最後有一個加分項,就在做 GraphQL 的時候有一個數據收集,在 GQ生態裡面有 GraphQL-extension,用起來非常好用,我們可以參考它來做一個自己的 extension 追蹤 GraphQL 查詢語句的執行情況,我們也可以使用第三方工具來做,如 apollo 的 trace。


最後來一起開個腦洞。
GraphQL 本身其實是一個標準,我們沒有必要一定要使用官方 提供的 GraphQL 引擎,我們可以根據自己的實際情況去實現自己的GraphQL。
重新回到 GraphQL 的執行的一個流程,我們在實現自己的 GraphQL 引擎時可以做到以下優化:

相同的查詢語句其實沒必要每次都去做驗證,這裡可以節約一點點查詢時間。既然是相同的查詢語句它的這種欄位收集其實沒有必要再去做收集,可以用一些比較簡單的方式去避免重複 collect fields。還有比較提升性能一點,官方的 graphql-js 去執行每一個欄位的 resolver 時是循環串列執行的,有沒有可能做到針對實際情況適當地並行執行 resolver 。

最後做一個總結,當宋小菜在使用 GraphQL 的時候,概括起來有以下六個特點:
1、單一入口,單端的入口方便前端做工程管理,避免後端做煩瑣的API 版本管理。
2、文檔,這個文檔可能在一定程度上能夠解決文檔同步的問題和前端開發閱讀的問題。
3、數據冗餘比較方便,減少前後端交流成本。
4、數據聚合。GraphQL 天生支援數據聚合。因為每一個類型綁定resolver,所以定義不同的 resolver,就可以拿到不同服務上的數據,可以做到不同類型數據源的數據拼裝。
5、MOCK,適當將 MOCK 的職責交到前端,或者前後端一起維護,而且維護起來比較簡單。所以說 MOCK 方便我們開發。
6、動態編輯做到實時部署,敏捷開發。實時部署很快做到線上數據的響應。
對於宋小菜的前後端合作工作流,直觀上可以看到這幾個變化:
- 前端從介面設計環節,向前介入到服務端的系統設計中的庫表結構評審環節,此時不僅能了解到庫表的欄位分布和業務含義,也能在庫表設計上就提出一些建議,幫助服務端輸出更友好的欄位類型和結構給前端,比如 精度和維度,這兩個是分開存,還是用逗號隔開,存一個 String,是有分別的;
- 服務端省去 Mock,省去膠水 API 的設計和維護,省去 Mock,專心做底層基於業務的系統拆分,提供更穩定的數據服務;
- 前端在介面評審之前,就可以在 GraphQL 的類型 Mock 上抽象大部分的欄位出來(服務端一定確定庫表結構,後續改動服務就會很小了),此時就可以把 DOM 頁面實現後,把佔位符的欄位就填進去了大部分,結構上在介面評審中雙方再核對調整一遍就好了;
- 前端由於有服務端領域邊界的支撐,可以針對特定領域及領域的組合,來封裝更有彈性的組件,組件的擴展性可以由配置決定,而不是某一個 API 決定,這個配置向下就是 GraphQL 的聚合能力。
關於第 4 點,我們仍然在探索嘗試,最終想要表達下我們團隊的工作理念,這一點很重要,無論事情是誰的,最終事情一定是公司的,無論一個技術推廣影響到誰或者撼動了誰的所謂原來立場所代表的利益,只要對公司研發團隊效率有利,而且有利於技術演進,有利於推動業務更快的走,那麼就要果斷嘗試。最終,為我們所有人的行為買單的是公司,但最最終,依然是我們自己。
(完)