GraphQL-前端開發的利劍與橋樑

基本概念

GraphQL

GraphQL 是一種用於 API 的查詢語言,由Facebook開發和開源,是使用基於類型系統來執行查詢的服務端運行時(類型系統由你的數據定義)。GraphQL並無和任何特定數據庫或者存儲引擎綁定,而是依靠你現有的代碼和數據支撐。前端

背景介紹

相信看了上面的基本概念,你們都是和我同樣一臉萌萌噠。因此這裏就須要介紹一下其產生的背景和緣由。vue

在咱們目前的先後端開發過程當中,大部分都是以http請求服務端接口的方式完成交互過程的。在這種場景下,每當需求變化,就須要修改或建立一個新的接口去知足特定的需求。sql

舉個栗子: 在一個商品詳情頁,當咱們須要獲取商品詳情時,服務端會給前端一個接口,例如:數據庫

https://www.example.com/getInfoById?infoId=000000npm

當前端請求接口時,會返回給一個固定格式的數據,例如:後端

{
    data:{
        title:'商品的標題',
        content:'商品的描述內容',
        special:'商品特色',
        price:'商品價格',
        image:'商品的圖片'
    }
}
複製代碼

前端接收到數據後,進行各類相應的處理展現,最終將包含有商品標題,商品描述,商品特色,商品價格,商品圖片信息的頁面展現給用戶。api

一切看起來都很美好,直到有一天……跨域

產品大搖大擺的走過來,輕描淡寫的說道:「能不能把商品的特色去掉,加一個商品的庫存,另外還須要再加一個賣家的模塊進去。包含賣家的名稱和頭像,能夠點進賣家的詳情頁,也不用太着急,午餐前上線就行。」數組

因而先後端坐在一塊兒開始商量,前端弱弱的說:「能不能改一下你的接口,把產品不要的都去掉,產品須要的都加上」。瀏覽器

後端內心說,你當我傻啊,因而一邊砸吧嘴一邊趕緊說道:「這樣改風險太大,好多數據都不在一個表,我很差查。這樣,詳情頁那個接口我就不改了,你不顯示不就完了嘛,萬一哪天產品那小子的小子再想起來加上,咱倆還得忙活。庫存再給你一個接口,賣家信息再給你一個接口,完美,就這麼定了。」

前端還想再說什麼,可後端的背影已經隨着產品越走越遠。

就在前端絕望之時,霹靂一聲震天響,graphql閃亮登場。

在graphql模式下,假設咱們的服務端部分已經部署完成,前端使用vue框架,那麼前端部分的請求就能夠簡化爲:

apollo: {
    goods: {
      query() {
        return gql`{
            goods(infoId:"${this.infoId}"){
              title
              content
              price
              image
          }
        }`
      }
    },
    store: {
      query() {
        return gql`{
            store(infoId:"${this.infoId}"){
              store
          }
        }`
      }
    },
    seller: {
      query() {
        return gql`{
            seller(infoId:"${this.infoId}"){
              name
              age
          }
        }`
      }
    }
  }

複製代碼

能夠看到graphql爲咱們定義了一種相似sql的查詢語言,而這種查詢語言是用於api的。和以前的數據請求處理不一樣,在這裏,咱們只要定義好須要的數據,其餘的再也不關心,咱們就能夠按需索取須要的數據。這對於咱們的開發提供了更大的自由與便利,只要數據支持,咱們就能夠擺脫對於服務端接口的依賴,提升生產效率,贏得自由,完成前端的逆襲。

先後端實踐

講完了故事,咱們開始講一些實際的乾貨。 對於graphql,網上已經有不少實踐經驗,如下部分是在參考完成實踐經驗並自我實踐後給出的總結概括。

服務端

服務端的技術選型,咱們使用了eggjs框架,配合egg-graphqlegg-graphql插件完成。

1.安裝依賴包
$ npm install --save egg-graphql
複製代碼
2.開啓插件
// config/plugin.js
exports.graphql = {
  enable: true,
  package: 'egg-graphql',
}; 
//開啓 cros 跨域訪問
exports.cors = { 
    enable: true, 
    package: 'egg-cors'
}
複製代碼
3.配置graphql路由和跨域
//config/config.default.js
// graphql路由
config.graphql = {
router: '/graphql',
// 是否加載到 app 上,默認開啓
app: true,
// 是否加載到 agent 上,默認關閉
agent: false,
// 是否加載開發者工具 graphiql, 默認開啓。路由同 router 字段。使用瀏覽器打開該可見。
graphiql: true,
// graphQL 路由前的攔截器
onPreGraphQL: function*(ctx) {},
// 開發工具 graphiQL 路由前的攔截器,建議用於作權限操做(如只提供開發者使用)
onPreGraphiQL: function*(ctx) {},
}
// cors跨域
config.cors = {    
    origin: '*',    
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
}
複製代碼
4.開啓graphql中間件
//config/config.default.js
exports.middleware = [ 'graphql' ];
複製代碼

項目配置告於段落。

5.編寫業務員代碼

下面開始寫代碼。 目錄結構以下:

├── app
│   ├── graphql                          //graphql 代碼,全部和graphql相關的代碼都在這裏,已經在前面作好了配置
│   │    └── common                   //通用類型定義,graphql有一套本身的系統類型,除此以外還能夠自定義
│   │     |    |── scalars                 //自定義類型定義
│   │     |    |  └──  date.js            // 日期類型實現
│   │     |   └── resolver.js            //合併全部全局類型定義
│   │     |   └── schema.graphql     // schema 定義
│   │    └──goods                       // 商品詳情的graphql模型
│   │         └── connector.js         //鏈接數據服務
│   │         └── resolver.js            //類型實現,和goods中schema.graphql定義的模型相對應,是其具體的實現
│   │         └── schema.graphql    //schema 定義,在這裏定義商品詳情數據對象
│   │    └──store                        // 庫存的graphql模型
│   │         └── connector.js         //鏈接數據服務
│   │         └── resolver.js            //類型實現
│   │         └── schema.graphql    //schema 定義,在這裏定義商品詳情數據對象
│   │    └──seller                       // 賣家信息的graphql模型
│   │         └── connector.js         //鏈接數據服務
│   │         └── resolver.js            //類型實現
│   │         └── schema.graphql    //schema 定義,在這裏定義商品詳情數據對象
│   │    └──query                       // 全部的查詢都會通過這裏,這裏是一個總的入口
│   │         └── schema.graphql    //schema 定義
│   ├── service
│   │   └── goods.js                    //商品詳情的具體實現
│   │   └── store.js                     //庫存的具體業務邏輯
│   │   └── seller.js                     //賣家信息的具體業務邏輯
│   └── router.js
複製代碼

app/graphql/query/schema.graphql是整個graphql查詢的總入口,全部須要查詢的對象都要在這裏定義。它的定義形式以下:

#定義查詢對象,在graphql裏的註釋使用#號
type Query {
goods(
  #查詢條件,至關於接口的入參商品id
  infoId: ID!
):Goods #Goods是在app/graphql/goods/schema.graphql中定義的商品詳情
}
複製代碼

在總入口中涉及到的查詢對象,都須要在graphql文件夾下創建相應的文件夾,如上文中提到的goods,就在app/graphql文件夾中存在相應的goods文件夾。goods文件夾包含三個部分:schema.graphql,resolve.js和connector.js。 schema.graphql中須要定義總入口中說起的Goods對象:

# 商品
type Goods {
    # 流水號
    infoId: ID!
    # 商品標題
    title:String!,
    # 商品內容
    content:'String!, #商品特色 special:'String!,
    #商品價格
    price:'nt!, #商品圖片 image:'String!,
}
複製代碼

graphql自帶一組默認標量類型,包括Int,Float,String,Boolean,ID。在定義字段時須要註明類型,這也是graphql的特色之一,是支持強類型的。若是非空,就在類型後面跟上一個!號。graphql還包括枚舉類型,列表和自定義類型,具體能夠查看相關文檔。

resolve.js是數據類型的具體實現,依賴connector.js完成:

'use strict'
module.exports = {
  Query: {
        goods(root, {infoId}, ctx) {
        return ctx.connector.goods.fetchById(infoId)
  }
}
複製代碼

connector.js是鏈接數據的具體實現,可使用dataloader來下降數據訪問頻次,提升性能:

'use strict'
//引入dataloader,是由facebook推出,能大幅下降數據庫的訪問頻次,常常在Graphql場景中使用
const DataLoader = require('dataloader')
class GoodsConnector {
    constructor(ctx) {  
        this.ctx = ctx  
        this.loader = new DataLoader(id=>this.fetch(id))
    }
    fetch(id) {  
        const goods = this.ctx.service.goods  
        return new Promise(function(resolve, reject) {    
            const goodsInfo = goods.getInfoById(id)    
            resolve([goodsInfo])  //注意這裏須要返回數組形式
        })
    }
    fetchById(id) {  
        return this.loader.load(id)
    }
}
module.exports = GoodsConnector
複製代碼

上面代碼中涉及的this.ctx.service.goods就是app/service文件夾下的goods.js文件導出的方法對象,也就是獲取數據的具體業務邏輯:

const Service = require('egg').Service
const {createAPI} = require('../util/request')//實現的http請求
class GoodsService extends Service {
// 獲取商品詳情
    async getInfoById(infoId) {
        const result = await createAPI(this, 'example/getInfoById', 'get', {infoId})
        return result
    }
}
module.exports = GoodsService

複製代碼

獲取數據能夠用你能實現的任何方式,能夠直接從數據庫獲取,也能夠用http從現有的接口獲取。 這樣一個使用egg框架實現的graphql服務就完成了。 下面說一下前端。

前端

咱們會使用vue配合Apollo完成前端搭建。

1 安裝依賴包
npm install --save vue-apollo apollo-client
複製代碼
2.引用apollo
import 'isomorphic-unfetch'//黑板敲重點,apollo-client中直接使用了fetch,引入兼容低版本瀏覽器
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'

複製代碼
3.配置連接
const httpLink = new HttpLink({
  // 須要配置一個絕對路徑
  uri: 'http://exzample.com/graphql',
})
複製代碼
4.建立ApolloClient實例和PROVIDER
// Create the apollo client
const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  connectToDevTools: true,
})
const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
})
複製代碼
4.在vue中引入使用
Vue.use(VueApollo);
複製代碼
5.根實例引用
var vm = new Vue({
      el: '#app',
      provide: apolloProvider.provide(),
      router,
      components: {
        app: App
      },
      render: createEle => createEle('app')
    })
複製代碼
6.使用
<script>
import gql from "graphql-tag"; 
export default { 
    data() { 
        return {
            goods:{},
            infoId:123123
        }; 
    }, 
    apollo: { 
        goods: {
            query() {
                return gql`{
                    goods(infoId:"${this.infoId}"){
                        title
                        content
                        price
                        image
                    }
                }`
            }
        },
     }
 }; 
 </script>
複製代碼

展望

graphql對於目前接口數量多,難維護,擴展成本高,數據格式不可預知,文檔難維護等問題給出了一個相對完善的方案,相信在將來,它將是咱們工做中不可或缺的一部分。

相關文章
相關標籤/搜索