凹凸技術揭祕:如何服務 toG 項目——數字人民幣項目前端總結

前言

toG 項目——一個在我等日常工作中極爲罕見、極爲神祕的項目領域,所有經歷過的人,都彷彿經受了一場狂風暴雨的洗禮,誰做誰知道。

而數字人民幣項目,光看名字就令人心生敬畏——新的貨幣形式、政府合作項目,充滿着未知與挑戰。事實也的確證明,這一不同尋常的項目,擁有從政府側溝通、產品策劃、業務投放、交互視覺設計、素材製作、前後端研發、測試到項目演練的長鏈路流程。而在這個流程中,波濤起伏的挑戰撲面而來。整個項目團隊從一開始措手不及的慌亂萌新,也漸漸被現實鞭撻成了兵來將擋水來土掩的銅牆鐵壁。

那麼這到底是個怎樣的項目,而作爲前端的我們又是如何應對的,本文將細細道來。


目錄

  • 項目特點
    • 多金
    • 緊急
    • 善變
  • 快速交付的前提
    • 梳理活動狀態流:透過現象看本質
    • 聚合活動數據流:中心化管理數據
    • 按需解耦:UI 複製,邏輯複用
  • 風險控制的措施
    • 添加模塊加載器
    • 定義合適的部署層次
    • 減少風險數量:對發版下手
    • 降低風險轉變故障的概率:只要跑得夠快,bug 就追不上我
    • 減少故障影響範圍:模塊劃分的奧義
    • 縮短故障影響時長:把客訴扼殺在搖籃裏
  • 結語

項目特點

截止文章發佈時,京東與地方政府協作的數字人民幣項目,已完成了蘇州、北京及成都三地的報名、抽籤及發放全流程。在活動入口根據地區定投的情況下,活動總 PV 達到了約6千萬。


本文中描述的數字人民幣項目,爲承接了京東主站 APP 中報名、中籤查詢流程的 H5 頁面。

本次項目的主要特點,可以用三個詞概括:多金緊急善變

多金

本次活動的預算高、單用戶收益高,每個中籤用戶可獲得200(成都可高達238)人民幣的赤裸裸收益,根據以往的經驗,高收益代表着高客訴風險,因此在活動流程及頁面內容展示方面必須做到極度嚴謹、毫無破綻,要求項目各角色具備極高的風險意識。

緊急

項目初期瘋狂壓縮的排期,對於項目既快速又高質的落地提出了巨大的挑戰。同時,在臨近政府交付期限,以及演練的過程中,常常出現不可抗的需求調整,且留給項目組的時間常常只有1到2天,如何安全、快速地迭代,又是一大考驗。

善變

在項目推進的1個多月中,大規模的需求變更總計達到了6次,其中有一次甚至砍掉了一大半的業務玩法,每一次的變動,都將給項目帶來不可預知的風險。如何在每次變動的過程中——尤其在活動進行時——儘量降低項目風險,也是需要充分考慮的。

這三個項目特點,轉化到前端肩上的挑戰,就是如何取得項目快速交付與風險控制的平衡。

快速交付的前提

前端實現項目快速交付的前提,也是許多活動開發常常忽略或者略過的前期階段——詳細的需求分析與架構設計。這一類短週期活動,在活動開發時對於架構設計必要性的要求並不高,一般只要滿足了功能、通過了測試,在一兩週的週期內不出現線上問題,就完成了這個項目的使命。至於架構設計的合理性與可擴展性,看起來並沒有那麼重要。

但在這樣一個快速迭代、流程複雜、隨時有可能調配人手協作的項目裏,前期的準備就顯得尤爲重要了。

梳理活動狀態流:透過現象看本質

通過數字人民幣整體交互稿可知,整個活動的交互流程包含了用戶報名環節、審覈環節、中籤結果查詢環節以及線下站外掃碼環節幾個階段,整個活動流程非常冗長且狀態十分複雜。


因此在進行活動具體邏輯的開發之前,第一步做的是梳理整個活動的狀態流,將交互流程中那些存在分叉和相交的流程節點進行梳理和整合。


舉個例子,數字人民幣項目的交互流程中,每個階段都存在一些異常的狀態,但實際上活動的核心流程中不應該包含這些異常的狀態流,因此在梳理過程中,我們將異常的狀態流抽離了出來,將這些異常狀態放在了其他的地方,如請求函數中,進行單獨的判斷,而核心流程只保留了諸如報名、審覈和中籤等關鍵狀態。


這一步驟自始至終的目標,都是使得最後呈現在代碼中的狀態流能儘量的簡潔和清晰,這樣在之後進入具體邏輯的開發時,可以不需要再花太多的精力聚焦在諸如“當前是什麼樣的流程和狀態”和“會不會有其他的狀態與當前狀態是相交或者重複的”的問題上面了。

聚合活動數據流:中心化管理數據

在本次數字人民幣項目代碼中,我們將頁面中所有核心數據都存放在最頂層的 Mobx 中進行統一管理,自上而下地或是直接地將數據分發至各組件中。

另外,需要避免子組件通過回調函數直接更改父組件的狀態或者數據,更推薦的做法是通過 action 更改 Mobx 中存放的狀態字段,通過 Mobx 自上而下地對需要更新的組件進行狀態的更新。

可以通過下圖看到,沒有對數據流進行聚合前,組件間的數據傳遞是混亂的、無章法的,這明顯不利於對項目代碼後續的迭代和維護。

過數據流的聚合,可以很爲方便地在代碼層面梳理清楚整個頁面的數據流走向,數據從哪裏來,到哪裏去,又產生了什麼影響,也就十分清晰了。

按需解耦:UI 複製,邏輯複用

在軟件開發和設計中,有一個很有名的思想“複製優於複用”,引用《“內源” over 中臺》中的解釋,即“追求複用會加深系統間的耦合度。在系統重構時,一種常見解決方式就是複製,在開源世界裏就是:方向不同,即可在其基礎上另起爐竈(fork)”,在數字人民幣項目中的 UI 層,我們也希望遵循同樣的思想,講究複製,而非複用。

“複製”的實質,其實是“解耦”。將相同類型不同樣式的UI結構代碼耦合在一個組件中,其實並不利於後續的維護和快速迭代。

在數字人民幣項目中,我們遵循的原則是“UI 複製,邏輯複用”,即相同類型的 UI 結構我們更傾向於另起爐竈,不同樣式的 UI 結構對應不同的組件文件,最後再通過模塊加載器將這些相同類型不同樣式的 UI 組件分發出去。並提取不同 UI 組件中的相同邏輯代碼,做到最大程度的邏輯複用。具體的做法在下文中會有詳細的描述。

風險控制的措施

在風險控制方面,我們主要從減少風險數量、降低風險轉變故障的概率和減少故障影響範圍三個方面來思考和實踐,以保證活動頁面的高可用。《高可用的本質》中提出了風險期望公式,如下圖所示:


文章《高可用的本質》提到,在上述風險期望公式中,控制風險主要從 nPRT 四個方向去考慮,nPRT分別對應『減少風險數量,n』、『降低風險變故障的概率,P』、『減小故障影響範圍,R』和『縮短故障影響時長,T』,我將在下面把其中的幾個方向與數字人民幣項目中相關實踐相結合,來闡述我們在本次活動中是如何通過控制風險來降低線上出現故障問題的可能性的。

減少風險數量:對發版下手

《高可用的本質》中提出,“所有事物都不是100%可靠的”,不管是人還是機器,都有犯錯的可能——開發會因爲粗心寫錯代碼,軟件會因爲系統BUG導致系統故障,硬件機器會因爲耐久度和異常環境導致功能異常和損壞。而從需求變動到活動上線,我們的代碼經歷的開發、編譯、打包、部署的流程中,都存在着人和機器犯錯的可能。《高可用的本質》中還提到,“從概率學角度分析,凡是有可能會出錯的,只要變化次數足夠多,最終出錯的概率會無限趨向於1”,結合文中的風險期望公式可知,需求變動的次數越多,風險期望就越高。

日常項目中,頻繁發版無疑將增加風險數量。

在此次項目中,我們考慮從規範提需流程,隔離運營數據與邏輯入手,以最大程度降低發版次數。

一、規範提需流程在需求提出時,嚴格評估需求變動的必要性和風險;需求確認時,與項目各角色同步信息,並通過郵件等方式進行統一的記錄。提高需求變動的門檻,過濾重要性較低的需求,降低需求變動頻率。這也是所有大型項目應該遵循的規範化流程。

二、隔離運營數據與邏輯首先明確一下兩個名詞的含義。運營數據指項目中與業務文案、視覺素材等相關的數據,邏輯指頁面主流程、交互相關的代碼文件。邏輯的部署,會經過編譯、打包、上傳、發佈幾個步驟,每發佈一次,都存在的一定的發版風險。本次數字人民幣活動中,通過運營數據與邏輯通過兩個獨立的平臺進行管理,來最大化減少邏輯發版次數。

運營數據管理方面,通過接入PPMS(運營內容管理平臺)系統,將頁面中可預見修改的模塊內容來源進行了可配置化處理。代碼上線後,如遇到了突發的非核心流程邏輯的需求變動,可通過更改配置項更新數據源來實現頁面展示內容的變動,而無需重新部署邏輯代碼,大大降低了運營內容頻繁的變動而可能引發的部署風險。

降低風險轉變故障的概率:只要跑得夠快,bug 就追不上我

活動頁面存在風險,是個無法避免的問題,但存在風險不代表着上線後一定會發生故障,只是存在出現故障的可能。爲了在上線前對已知的可控的風險項進行排查和檢測,可制定一份上線前 checklist。

數字人民幣項目的 checklist 包含兩類內容,一類是通用的檢查項,一類是項目特有的檢查項,最大化覆蓋可預見的風險點,解放開發心智專注開發。


減少故障影響範圍:模塊劃分的奧義

減少故障影響範圍,最主要的手段就是對邏輯進行模塊化隔離。

在此項目中,主要從兩個方面着手:代碼層面的模塊隔離,以及部署層面的模塊隔離。

添加模塊加載器

在模塊設計層面,我們添加了模塊加載器,對相同類型的模塊進行了統一的收集和分發,將這些模塊進行了完全的解耦和隔離。 以往在進行模塊開發時,我們通常會有思維慣性,習慣性地從局部去思考和設計模塊的使用,且常常忽略重構的重要性。我們來看一個例子,當我們創建一個頭部組件時,第一時間會想到以下的實現方式:

class Header extends React.Component {
  render () {
    return (
      <div className='header'>I am header</div>
    )
  }
}

目前看似一切正常,但不幸的是,需求往往是反覆多變的,假設有一天,這個頭部組件需要根據不同的狀態增加一個商品的展示,那麼很自然地,代碼將會變成以下的模樣:

class Header extends React.Component {
  render () {
    const { isGoodShow } = this.props
    return (
      <div className='header'>
        I am header
        {
          isGoodShow ? (
            <div className='good'>
              <img className='good-url' src='url' />
              <span className='good-title'>頭部商品</span>
            </div>
          ) : null
        }
      </div>

    )
  }
}

我們可以看到,我們將頭部商品展示的邏輯,直接寫在了唯一的頭部組件中,由於當前的組件並不複雜,所以看似代碼邏輯十分清晰明瞭,並無任何問題。

但試想一下,當頭部組件中需要繼續根據不同條件出現其他各種各樣的模塊時,整個頭部組件會冗雜着這些模塊的所有邏輯。 在未來快速的迭代和需求變更中,我們有可能需要對頭部組件中某個模塊的代碼進行更改和調整,在這個時候,面對冗雜着所有模塊代碼的頭部組件,調整代碼導致出錯的概率將大大提高。

在數字人民幣項目中的情況就是如此,在項目中同樣存在頭部組件,頭部組件存在着未登錄、未開啓定位、未報名、審覈中、已中籤和未中籤六種情況,如果我們將六種情況都耦合在一個頭部組件中,顯然是不合適的。

計算機領域有句名言:“計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決”, 因此在數字人民幣的開發過程中,我們對頭部組件以及存在類似情況的模塊組件都進行了一次重構,在組件的調用層和組件間建立了一層中間件,我們通俗地稱它爲“模塊加載器”。

模塊加載器的職責很簡單,收集所有的頭部組件,並將他們根據不同的情況進行分發,我們來看下面一段代碼:

import BaseHeader from './base'

class HeaderA extends React.Component /***/ }
class HeaderB extends React.Component /***/ }
class HeaderC extends React.Component /***/ }

class HeaderModuleLoader extends React.Component {
  render () {
    let Content
    swtich (headerType) {
      case 'a':
        Content = <HeaderA />
        break
      case 'b':
        Content = <HeaderB />
        break
      default:
        return <HeaderC />
    }

    return (
      <div className='header'>
        <BaseHeader />
        {Content}
      </div>

    )
  }
}

在上面這段代碼中,我們很好地對頭部組件進行了分類,將它們可複用的部分提取在了BaseHeader組件中,而其他不同情況下展示的內容對應地封裝在了不同的頭部組件中,通過switch...case...進行對應情況的分發。

上面這段代碼,本質上是策略模式的一種應用HeaderModuleLoader模塊加載器會返回不同的內容,但它們都是屬於header中的一種,只不過我們在模塊加載器中進行了不同的策略判斷和分發。

經過這種模式的處理,我們便成功地對不同情況的頭部組件進行了解耦,當之後再出現需要調整或更改某個頭部組件模塊時,我們只需去到對應HeaderA或者HeaderB中進行對應的調整即可,也不會影響整個頭部組件的功能和使用。


同時,我們還對項目中其他擁有相同情況的模塊進行了一併的改造。在之後數字人民幣的迭代和調整中,出現過很多類似“審覈狀態下的頭部,我想加一段文字”的需求變動,在以前,我們需要在唯一的頭部組件中,去通過條件語句判斷活動狀態來展示新增或是刪減的內容。

但在經過改造後,我們只需要進到對應狀態的頭部組件去新增或者刪減代碼即可,不用再去寫任何多餘的判斷,也不需要考慮當前的修改會不會影響到其他的內容和邏輯,可見,這種設計模式給我們帶來了極大的開發便利和效率提升。

總的來說,爲了前端模塊未來的可拓展性和可維護性,我們需要對這些頻繁變更模塊的UI層進行更徹底的解耦,對邏輯層進行更徹底的複用。

定義合適的部署層次

秉承着雞蛋永遠不放一個籃子裏,大樹也永遠不抱死一棵的理念,在這次數字人民幣活動中,針對部署隔離,我們主要做了兩件事:運營數據與邏輯部署的隔離,及頁面級部署的隔離。

運營數據與邏輯部署的隔離,已在「減少風險數量」一節中有了詳細的說明,此處不再贅述。

頁面級部署隔離方面,我們參考微前端思路,將一些和主流程關係不大的流程和模塊進行了頁面層級的抽離,將原本唯一的活動頁拆分成了站內H5頁、站外H5頁、數字人民幣APP下載頁以及異常情況處理頁4個部分。頁面間利用URL查詢參數進行通訊。每個頁面都進行單獨的開發和部署上線。

而這樣定義部署層次的原因主要是因爲以下兩點:

  1. 從源碼層面來看,這樣做主要是爲了保證主流程頁面邏輯的乾淨和簡練,主流程頁面的代碼是整個活動的核心,將越多無關緊要的細枝末節進行剔除和隔離,越能保證主流程邏輯調理的清晰和乾淨,一定程度上提高了主流程代碼的穩定性和可讀性。
  2. 從風險層面來看,將一個活動頁拆分成四個是爲了將風險分散,打散後,不管站外H5出現了任何問題,都無法對核心流程的站內H5產生影響,反過來也是同理。這和投資理財要做好組合選擇是同個道理,做好風險對沖,是控制風險中重要的一環。

縮短故障影響時長:把客訴扼殺在搖籃裏

很多時候,一個項目,經過完整測試用例的捶打還遠遠不夠,有些隱藏的問題,不經過大流量的沖刷,是難以顯現的。例如此次項目,在演練過程中出現的,『活動太火爆』界面出現概率超出預期,是通過少量賬號及少量訪問量難以暴露的問題。

產品&業務同學:“有用戶反饋說頁面跳轉到太火爆了,是接口有異常情況嗎?” 後端同學:“後端監控這邊沒有看到有異常的數據,有可能是前端這邊請求超時了。” 產品&業務同學:“前端同學這邊看看是不是有這個問題。” 前端同學:“額……前端沒辦法看到有沒有這個情況。”

線上監控的接入,則可以在一定程度上提早發現這一類問題,並提前進行相應的處理,及時止損,與客訴賽跑,甚至把客訴扼殺在搖籃中。

而此次項目中,由於工期限制,前端監控並沒有做到妥善的規劃與接入。在項目後期有時間接入時,又遭遇項目代碼迭代安全性風險,及測試成本的挑戰。在項目開發規範中對於接入監控的非強制性,以及監控接入的時間與心智成本較高,是根本原因所在。

團隊已在京東商城 PC 首頁項目中產出了最佳實踐:接口可用性監控測速監控以及代碼異常監控相結合。通過接口可用性監控,一旦出現接口低可用率或者異常調用量的情況,通過電話以及消息推送的方式對開發者進行實時提醒。而測速監控則對於頁面及接口的性能優劣起着監控作用。代碼異常監控可上報頁面的報錯信息,由開發者進行篩選和甄別,判斷有沒有出現開發時沒有被排查到的線上問題。

基於最佳實踐,考慮爲開發者提供一個可以快速接入監控系統的 SDK,以此來解決『如何快速地接入這些監控系統』的問題。將一些必要的接入操作整合在一套 SDK 中,並對監控系統原本提供的一些業務方法進行拓展,使其更靈活,適用更多的業務場景。

結語

正如開頭所說,本次前端側的挑戰在於如何取得項目快速交付與風險控制的平衡,本次項目中我們根據以往的大促經驗及理論進行了針對性的思考與實踐,基本實現了把風險控制在了可控範圍內。但目前所有的措施還是依賴於執行人的人工操作以及主觀意識,我們會基於此持續探索優化,以全面接入數字化平臺流程爲目標,最大化進行風險管控,以更好的持續性地對 T 級互動進行支持。


本文分享自微信公衆號 - 凹凸實驗室(AOTULabs)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章