Scott:各位大牛,大神,大咖大家下午好,我是 Scott,也是這次杭州第一屆 GraphQLParty 的發起人。
先回到會議的主題,為什麼會開這麼一個會議?原本這個會議報名的時候希望可以控制到 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 查詢語句的追蹤。可以看到,像我們執行這麼一個查詢語句,拿到的資料結果,執行時間,這一個查詢語句的詳情都能看到,同時還可以看到每一個欄位查詢速度如何。又比如說,像這一個介面,它繫結兩個服務,一個服務是囤貨單的服務,還有一個服務是供應商資訊服務,這樣子可以看到每一個查詢欄位它追蹤到這種執行效率怎麼樣的,可以根據這個查詢結果來告知後端同學做優化。
還有我們的部分自定義 Mock,這就是整個 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 的時候,概括起來有以下六個特點:
- 單一入口,單端的入口方便前端做工程管理,避免後端做煩瑣的API 版本管理。
- 文件,這個文件可能在一定程度上能夠解決文件同步的問題和前端開發閱讀的問題。
- 資料冗餘比較方便,減少前後端交流成本。
- 資料聚合。GraphQL 天生支援資料聚合。因為每一個型別繫結resolver,所以定義不同的 resolver,就可以拿到不同服務上的資料,可以做到不同型別資料來源的資料拼裝。
- MOCK,適當將 MOCK 的職責交到前端,或者前後端一起維護,而且維護起來比較簡單。所以說 MOCK 方便我們開發。
- 動態編輯做到實時部署,敏捷開發。實時部署很快做到線上資料的響應。
Scott:回答觀眾的問題(重新總結的版本)
對於宋小菜的前後端合作工作流,直觀上可以看到這幾個變化:
- 前端從介面設計環節,向前介入到服務端的系統設計中的庫表結構評審環節,此時不僅能瞭解到庫表的欄位分佈和業務含義,也能在庫表設計上就提出一些建議,幫助服務端輸出更友好的欄位型別和結構給前端,比如 精度和維度,這兩個是分開存,還是用逗號隔開,存一個 String,是有分別的;
- 服務端省去 Mock,省去膠水 API 的設計和維護,省去 Mock,節約的時間可以專心做底層基於業務的系統拆分,提供更穩定的資料服務,構建更健壯相容的底層架構;
- 前端在介面評審之前,就可以在 GraphQL 的自定義型別 Mock 上抽象大部分的欄位出來(服務端一但確定庫表結構,後續改動的可能性就會很小了),此時就可以把 DOM 頁面實現後,把佔位符的欄位就填進去了大部分,最終結構上在介面評審環節雙方針對介面特殊性,再核對調整一遍就好了;
- 前端由於有服務端領域邊界的支撐,可以針對特定領域及領域的組合,來封裝更有彈性的元件,元件的擴充套件性可以由配置決定,而不是某一個 API 決定,這個配置向下就是 GraphQL 的聚合能力。
關於第 3 點,是需要前後端不斷磨合的,關於第 4 點,我們仍然在探索嘗試,最終想要表達下我們前端團隊的做事的一些理念,我個人認為這一點很重要,尤其對於初創團隊。團隊裡面,無論事情看上去是屬於誰的,最終事情一定是公司的,無論一個技術推廣影響到誰或者撼動了誰的所謂原來立場所代表的利益,只要對公司研發團隊效率有利,有利於技術演進,有利於推動業務更快的走,那麼就要果斷嘗試。最終,為我們所有人的行動買單的是公司,但最最終,依然是我們自己。