Vue 灰度釋出新功能的那些事

南城大前端發表於2022-07-12

前言

什麼是灰度釋出?百度百科的解釋如下。

灰度釋出是指在黑與白之間,能夠平滑過渡的一種釋出方式。AB test就是一種灰度釋出方式,讓一部分使用者繼續用A,一部分使用者開始用B,如果使用者對B沒有什麼反對意見,那麼逐步擴大範圍,把所有使用者都遷移到B上面來。灰度釋出可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

從上可以看出,灰度釋出的主要作用有以下幾點:

  1. 降低直接全量釋出帶來的影響,讓少部分使用者先使用新版本,如發現問題則及時做好修復,驗證無重大問題則全量釋出新功能
  2. 通過新老版本的資料對比,決定新版本是否需要全量釋出

概述

灰度釋出的方式有很多,按端可以區分服務端,客戶端,Web前端都可以做,沒有最好,只有更適合自己的業務場景。

如上可以看到常見的幾種灰度釋出的方式,都有各自的優缺點,由於我們公司有完善的大資料AB test方案,所以前端只需關注介面返回的欄位標識,來做具體的頁面載入邏輯,今天重點講述在前端中使用Vue框架中如何做灰度釋出。在Vue中主要可以分為以下兩種情況:

元件級別:

  1. 元件級別動態控制只需後端回傳對應方案標識即可。

頁面級別:

  1. 前端頁面訪問地址不變,同後端人員約定好AB test 標記欄位,前端根據欄位返回不同的內容載入對應的頁面。
  2. 新舊功能區分兩個頁面地址,跳轉頁面地址由後端控制,此方案前端不需要太多改動,此文就不多說明。

先來看看日常處理的方式,一個頁面可能會存在多個地方判斷AB test 邏輯,或者是更多的AB test同時進行,這樣的頁面程式碼邏輯複雜度相對比較高,也不夠整潔易懂,當有新的AB test加入或者有AB test需要決策的時候,修改程式碼的成本較高,降低了程式碼維護的效率。

<template>
    ...
    <test-a v-if="testA" />
    <test-b v-else-if="testB" />
    ...
    <div v-if="testA">
        ...
    </div>
    <div v-else-if="testB">
        ...
    </div>
    ...
</template>
<script>
    ...
    if (testA) {
        ...    } else if (testB) {
        ...
    }
</script>

接下來就開始我們今天文章的正題,看看有哪些方式可以解決以上的問題。

元件級別

如只是簡單的兩個小元件功能的灰度則可以直接用 v-if 處理

<testA v-if="testA" />
<testB v-else />

如有多個功能同時測試,可以通過 Vue 的<component>元素加一個特殊的 isattribute 來實現,currentTabComponent 可基於介面獲取或其他前端計算得出。

<component :is="currentTabComponent"></component>

頁面級別

方案一 新增入口頁面分發

新增入口頁面,將新舊版本頁面升級為元件的方式引入,入口頁面增加介面查詢,通過 v-if 或通過 Vue 的<component> 元素加一個特殊的 is 屬性來載入頁面元件。如下是通過介面查詢程式碼示例,通過介面前置查詢會帶來一定的介面延遲載入,取決於介面的響應速度,我們也可以通過在URL增加引數獲取,這時的URL由後端拼接好引數再返回,這樣就可以避免一次介面查詢。

<template>
   <component :is="testId"></component>
</template>
<script>
  import IndexA from './index-a'
  import IndexB from './index-b'
  import {
    getTestID
  } from '@/api/getTestID'

  export default {
    name: 'index',
    components: {
      'index-a': IndexA,
      'index-b': IndexB
    },
    data() {
      return {
        testId: ''
      }
    },
    created() {
      this.getTestID()
    },
    methods: {
      async getTestID() {
        const { testId } = await getTestID({
          xxx: xxx
        })
        this.testId = testId
      }
    }
  }
</script>

這裡直接這樣載入頁面級元件會導致此檔案體積加大,可以將頁面元件的載入方式改為非同步元件,提升頁面載入速度。

components: {
    'index-a': () => import(/* webpackChunkName: "index-a" */ './index-a'),
    'index-b': () => import(/* webpackChunkName: "index-b" */ './index-b')
}

方案二 高階元件方案

在路由配置中從介面獲取灰度標識資料,進行路由分發。如果不想額外增加介面查詢的開銷,也可以將標識資料從URL引數返回,此方式需要提前拼接好引數。

高階元件的好處是所有需要灰度的載入邏輯都在路由配置檔案中,統一維護,元件也可複用,不需要每個需要灰度的頁面都增加一個入口檔案。

元件程式碼

<template>
  <component :is="com" />
</template>
<script>
export default {
  name: 'DynamicLoadComponent',
  props: {
    renderComponent: {
      type: Promise
    }
  },
  data() {
    return {
      com: () => this.renderComponent
    }
  }
};
</script>

router.js 配置

{
  path: 'originPath',
  component: () => import('@/views/components/DynamicLoadComponent'),
  name: 'originPath',
  props: (route) => ({
    renderComponent: new Promise((resolve, reject) => {
      // 根據 route 拼接引數獲取載入頁面
      if (route.query.testA) {
          resolve(import('@/views/testA'));
      } else {
          resolve(import('@/views/testB'));
      }
      // OR 根據介面返回標識動態載入頁面
      getAPIData()
        .then((response) => {
          if (response.testA) {
            resolve(import('@/views/testA'));
          } else {
            resolve(import('@/views/testB'));
          }
        })
        .catch(reject);
    }),
  })
}

方案三 動態Router.js引入

如果是有大面積的頁面替換,可採用這種方式。例如,後端開發語言更換導致介面地址及返回的欄位內容都發生變化,這樣會有一段時間的過渡使用,開發完一個頁面上線一個頁面,就可能會有5個頁面使用新的方案,5個頁面還是保留原始方案的情況。

改造router.js,將原始路由配置抽離到default.js中,再新建java.js將新方案路由配置寫入,基於前端計算或介面返回標識動態載入路由配置檔案。

import Vue from 'vue'
import Router from 'vue-router'
import { isHitJavaAPI } from '@/config'

Vue.use(Router)

const router = new Router({
  mode: 'history'
})

const computedRouterDirectory = (routeFile) => {
   let routerConfig;
   const requireRouter = require.context('.', false, /\.js$/);
   routerConfig = requireRouter.keys().filter(file => file === `./${routeFile}.js`)[0];
   if (routerConfig) {
     routerConfig = requireRouter(routerConfig)
     routerConfig.default && router.addRoutes(routerConfig.default);
  }
}

if (isHitJavaAPI()) {
   computedRouterDirectory('java')
 } else {
   computedRouterDirectory('default')
}

isHitJavaAPI方法中是命中灰度的邏輯,如果這裡是前端做灰度,可基於deviceID或UA等計算。如果這裡是呼叫介面獲取方案則需改為同步呼叫。

總結

本文主要介紹了頁面級別的幾個灰度方案,每個方案的試用場景都有各自的優缺點,如新增入口檔案,主要是針對頁面變動較大且當前專案只會有一個在進行中的灰度測試;高階元件適用於當前專案有多個進行中的灰度測試,則可複用元件;動態載入路由配置檔案主要針對於當前專案有大規模的頁面UI或邏輯更換灰度測試;通過以上幾種方案都可極大的提升程式碼的可維護性以及解耦灰度邏輯和業務程式碼邏輯,當灰度測試沒有問題需全量上線時,我們只需修改入口邏輯即可,無需在業務程式碼中去逐個修改灰度邏輯。

除開本文所介紹的幾種方式,也還有其他的載入方式,如路由鉤子函式攔截後做動態跳轉,或者請求到後端,後端做重定向處理等。每個方式都有各自的優缺點,就看是不是你當前場景最合適的方案。如果你有其他的方案,歡迎留言和我們交流~

參考

Vue Router根據後臺資料載入不同的元件
components-dynamic-async

相關文章