記錄一個從枯燥學習 GraphQL 的過程,到發現項目 Gitter,模仿項目 Github-Trending-API,最後作一個本身的學習項目 Github-Trending-GraphQL。javascript
一開始我是這樣想的,最後我是這樣作的,覆盤整個學習過程。
html
graphql 是什麼? 在以前的項目中咱們主要使用 graphql 來作已有接口數據的合併,這個主要處理已有 rest 相關服務接口的狀況下,咱們作了一箇中間數據處理層。
最近在思考團隊服務項目開發的時候,由於在開發中若是基於 rest 接口來開發的會,會定義不少路由。爲了偷懶不去定義路由,因而決定在項目中使用 graphql (其實只是爲了裝B,我在項目中用了最新的XX技術),中間還有一些其餘的思考。java
Graphql 模型有三種類型的操做。node
查詢數據(R)。git
# standard query { field } # shorthand { fields }
新增、更新或刪除數據(CUD)。github
mutation { do( arguments ) { fields } }
表示能夠訪問的資源。數據庫
# Repository 包含項目的內容 # Implements # Connections # Fields
學不動了,省略....npm
在枯燥的文檔學習過程當中,中間看到一個博客是推薦本身的小程序 gitter
,出於習慣抓了一下小程序的請求,發現了趨勢排行是經過 github-trending-api.now.sh 獲取的數據,接着就找到了這個 API 對應的項目 github-trending-api。
在這以前我也看過幾回 GitHub GraphQL API,只是趨於時間與其餘因素(懶),一直沒有使用落實到實際的項目中。發現官方沒有提供 Trending API,github-trending-api 項目新增了 V3 中的Trending API,我是否是能夠模仿該項目提供一個 GraphQL API。
帶着兩個目的開始一個新項目:json
最簡單的實現方式就是提供一個 GraphQL server,而後直接請求 github-trending-api.now.sh 。這種用法對於項目已有微服務的團隊,能夠利用中間服務層來合併數據請求,以及嵌套數據查詢等。
GraphQL server 使用的是 Apollo Server,用它來建立一個 Node 服務,定義好 Schema,增長 resolver 解析函數。小程序
在一開始學習的基礎只是派上用場,GitHub Trending 主要提供兩個方面,一個是 Repository ,另一個是 Developer。
type Repository { author: String contributors: [Contributor] currentPeriodStars: Int description: String forks: Int language: Lang name: String stars: Int url: String }
Repository
中除了基本的 scalar type
還有兩個是 contributor 和 language,一個數組數據,一個是對象,繼續細分類型下去就獲得了
type Contributor { avatar: String url: String username: String } type Lang { name: String color: String }
Developer
分析數據後同樣獲得一個數據結構
type Developer { avatar: String name: String repository: RepositoryMini username: String url: String }
其中項目倉庫是一個對象數據,細分下來能夠獲得一個
type RepositoryMini { description: String name: String url: String }
定義好了基本數據類型 Repository 和 Developer 之後,須要對外提供一個統一的 Query
,因而獲得了一個新的根數據類型
type Query { repositories: [Repository], developers: [Developer] }
實際的查詢趨勢過程當中咱們還會增長參數,一個參數是 language,一個參數 since,其中 since 只能取 daily、 weekly、 monthly ,但實際也能取其它值,只是默認的仍是 daily。修改後獲得了下面的結果
type Query { repositories(language: String, since: String): [Repository], developers(language: String, since: String): [Developer] }
若是要驗證 since 只能取三個值中的一直,須要新增一個枚舉類型
type Query { repositories(language: String, since: Since): [Repository], developers(language: String, since: Since): [Developer] } enum Since { daily weekly monthly }
上述寫法實際過程當中可能還會有這樣一個問題,若是要同時查詢得到 Repository 和 Developer 的數據,須要按照篩選條件查詢的適合,須要重複傳遞參數,再提高一下這兩個類型實際是屬於類型 Trending 的。新增一個類型
type Trending { repositories: [Repository] developers: [Developer] }
根查詢 Query
也能夠修改一下了
type Query { trending(language: String, since: String): Trending }
按照最終咱們定義好的數據結構,咱們能夠發起一個這樣的 query
{ Trending(language: "javascript", since: "daily") { repositories { name author description language { name color } forks stars contributors { avatar url username } currentPeriodStars url } developers { avatar name repository { url name description } username url } } }
若是把 language 和 since 定義在 variables
中,寫法就變成了下面這樣
# 如下請求只獲取了趨勢倉庫名稱 # query query getTrending($language: String, $since: String) { trending(language: $language, since: $since) { repositories { name } } } # variables { "language": "javascript", "since": "daily" }
query
和 variables
會做爲 request payload 放置在 body 中,其中把自定義的操做方法 operationName
設置爲 getTrending
fetch("https://trending.now.sh", { "credentials": "omit", "headers": { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "content-type": "application/json" }, "referrer": "http://localhost:4000/", "referrerPolicy": "no-referrer-when-downgrade", "body": "{\"operationName\":\"getTrending\",\"variables\":{\"language\":\"javascript\",\"since\":\"daily\"},\"query\":\"query getTrending($language: String, $since: String) {\\n trending(language: $language, since: $since) {\\n repositories {\\n name\\n }\\n }\\n}\\n\"}", "method": "POST", "mode": "cors" });
這裏用的是 Apollo server,服務收到請求之後,會解析 body 參數。會按照嵌套依次調用 resolver 處理業務邏輯,首先會進入 trending ,接着同時執行 repository 和 developer。
按照根查詢定義好的數據結構,tending 解析器會收到兩個參數,language 和 since。repository 和 developer 也要使用這兩個參數如何處理呢?
// resolver { Query: { trending(parent, args, context, info) { // args => { language: '', since: '' } // parent 參數是能夠接收到上層解析器的結果,咱們能夠把 trending 中收到的數據傳遞給子解析器 return { language, since } } }, Trending: { repositories(parent, args, context, info) { // parent => { language: '', since: '' } }, developer(parent, args, context, info) { // parent => { language: '', since: '' } }, } }
解析器按照前文分析的數據,咱們能夠直接請求 github-trending-api.now.sh 數據接口拿到數據,這裏咱們本着學習爲目的,GitHub Trending 是經過 SSR 輸出的頁面,數據只能本身分析網頁,抓取html頁面之後分析頁面結構得到本身須要的數據。
export async function fetchRepository() { // 分析html } export async function fetchDeveloper() { // 分析html } export async function fetchLanguage() { // 分析html }
具體的分析 html 過程不作分析,使用了 cheerio,用法相似 JQuery。這中間也會有一些須要注意的問題
請求過程很慢。
每次請求都會再次請求 Github Trending 的頁面,而後還要分析頁面,這個過程實際上是比較費時的。咱們若是把請求分析後的數據按照查詢條件緩存起來,下一次請求直接就從緩存中拿數據,這樣就快不少。(倉庫和開發者趨勢會隔段時間更新,咱們緩存一小時;語言變化小,咱們緩存了一天的時間)
語言包緩存。
請求倉庫和開發者的適合,檢測語言緩存是否存在,不存在先緩存一次,後續再次請求倉庫和開發者或者直接請求語言包就會直接命中緩存
有了緩存就可能出現緩存失效的問題,咱們新增一個刷新緩存的方法,能夠按照指定鍵名來更新緩存,也能夠不傳遞參數清理所有緩存。
GraphQL 根處理方法除了 Query
,還有一個 Mutation
。對應到的數據庫增刪改查上面的話,Query
對應的是 R
,Mutation
對應的是 CUD
。咱們要新增的 refresh
的操做是刪除緩存,主要針對倉庫和開發者緩存,清理之後咱們只關心成功失敗與否,因此這裏咱們能夠返回一個布爾值
type Mutation { refresh(key: String, language: String, since: String): Boolean }
解析器中也須要添加對應的處理方法
{ Mutation: { refresh(parent, args, context, info) { // do something } } }
從一開始的需求分析,咱們須要開發一個 Github Trending GraphQL API。咱們利用了以前學習的 GraphQL 的基礎知識,也熟悉了 GraphQL 的工具 Apollo Server,很方便的開發出了對應的API,後續爲了優化請求,咱們新增了緩存策略,以及清除緩存策略。
到這裏咱們的項目 github-trending-graphql
就能夠提交到 GitHub 倉庫中了,對於一個完美的開源項目還有不少事情要作,可是對於一個 GraphQL 的示例差很少已經可使用了。
一上來就直接看代碼是枯燥的,因而咱們還須要部署一個 Demo,這樣帶着使用來熟悉就更容易讓人理解了。如何簡單的部署 Demo 又成爲了一個問題?
trending.now.sh 的部署看域名應該就能猜到使用的是 now
的無服務部署方式。使用方式文檔已經講述的很清楚了。但這中間也仍是須要注意一些細節
對於項目部署,咱們須要首先在項目根目錄創建一個 now.json
{ "version": 2, "alias": ["trending.now.sh"], "builds": [{ "src": "src/server.js", "use": "@now/node-server" }], "routes": [{ "src": "/", "dest": "/src/server.js" }] }
alias
這裏配置上 now.sh 的別名是不會直接生效的,這裏只是方便備忘。server.js 是一個須要執行的文件,因而咱們將 version
設置爲 2,接下來咱們就能夠在配置中添加 builds
了,對於普通 js 可指定文件使用 @now/node
,這裏的 server.js 是開啓一個 Node 服務,因此須要使用 @now/node-server
。
部署成功之後咱們得到了一個 github-trending-graphql-[hash].now.sh 的項目訪問地址,若是要訪問到項目的實際功能,還須要點開兩次兩次得到項目功能地址 github-trending-graphql-[hash].now.sh/src/server.js ,若是要直接使用域名直接訪問功能,咱們這裏就須要添加上述配置 route
。
每一次部署都會產生一個新的鏡像,也會獲得一個新的二級域名,若是咱們要分享出去不管是本身部署仍是用戶使用體驗都不是很好,咱們能夠爲本身的項目設置一個別名,這裏咱們爲當前項目設置的別名就是 trending.now.sh 。
每次部署的時候咱們須要作的工做就是 now && now alias
,now alias 須要指定當前部署得到的項目域名,以及須要設置的別名,$(now) 能夠得到部署後得到的域名,因而上述命名就修改爲 now alias $(now) trending.now.sh
了,添加 package.json 中,每次部署只須要執行一下 npm run now
。
github trending graphql api
online demo