圖片
大家都知道,詳情頁承載了站內的核心流量。它的量級到底有多大呢?
我們來看一下,日均播放次數數億次,這么大的流量,其重要程度可想而知。
在這樣一個頁面,每一個功能都是大量業務的匯總點。
作為用戶核心消費場景,詳情頁不僅需要承接各種業務的轉化,還要負責展示各業務在播放頁的功能。
可以說,播放頁的代碼復雜度屬于客戶端最高的代碼之一,這不僅因為播放頁本身的功能復雜,還因為它需要融合大量外部業務功能。
復雜的功能自然會帶來較高的代碼復雜度,而高代碼復雜度往往意味著高代碼維護成本。
圖片
我們來看一下沒有做這個項目之前的狀態。如圖所示,他們分別為三個業務團隊各自維護。頁面間相互獨立。能力無法復用。
圖片
通過這個項目,我們要將他們融合成了一個頁面。產品的訴求就是將他們融合為一個,來達到多業務形態統一的目標。
圖片
但是,這三個詳情頁并不像產品想象的那么簡單。
每個業務都有自己的特殊形態,如大型活動態、主客態、播單態、PUGV/OGV態等一系列業務形態。
每種形態都有自己的特殊邏輯,而且這些業務形態間還可以互相切換。
為了更好地達成目標,我們需要進行如下思考:
要解決多業務形態不統一的問題。例如,產品既想要UGC大型活動的能力,又想要OGV的多視角功能。
但這兩個能力在之前分別是兩個業務團隊各自開發的,無法復用,產品在業務選擇上無法兼得。
要解決迭代方式不統一的問題。例如,進度條體驗優化需求,產品在給UGC團隊提需求的同時,還要復制一份給OGV團隊。
兩個業務方的開發和測試都需要進入這個項目,并且雙方的開發進度和排期可能不一致。如果產品強烈要求同一版本上線,還需要協調各方資源。
要解決如何保障穩定性的問題。例如,多團隊協作,之前都是組內同事協作開發,現在融入了兩個新的業務團隊,我們該如何保障穩定性。
要解決如何讓新人快速上手的問題。正常情況下,新人想要進入開發必須對這個系統足夠了解后才行。
更何況現在變成了三個業務融合的頁面。有沒有一種手段,讓新人無需關心復雜的業務形態和業務邏輯,只需要關注自己的需求?
針對以上問題,我們可以總結出通用詳情頁框架必須滿足以上三點,分別為:復用性,靈活性,穩定性
圖片
接下來我們繼續對多業務形態進行分析。
首先我們從橫向上進行拆解,通過對比,我們可以發現
多業務形態間其實有很多的相同模塊。如互動,彈幕發送框,相關推薦等。
從縱向上進行拆解,我們也可以發現很多相同模塊,如彈窗管理器,主題組件,轉場組件等。
那么從橫向和縱向上我們發現,多種業務形態間其實有很多可以復用的能力。
圖片
基于前面的思考,我們設計了一套通用詳情頁的框架。將其分為三層:
這樣我們就初步解決了復用性的問題,但是隨之而來的就是靈活性問題。
圖片
我們以實際場景為例,相關推薦模塊在課堂態不展示,但是在ugc和ogv下需要展示,另外他的點擊事件在ugc和ogv下還會出現差異。
同時相關推薦模塊還強依賴簡介模塊。因為簡介模塊也是一個通用組件,業務方可以自由替換。
如果哪天業務方替換了了簡介模塊,那相關推薦模塊將無法正常運行。
從相關推薦這個例子我們可以得出如果想讓業務模塊復用,必須滿足兩個條件。
圖片
因此,我們需要在流程和模塊中加入依賴注入的能力,用于業務方實現差異化邏輯。
業務方可自行插入自己的業務邏輯,并選擇或替換業務模塊。來解決模塊間的耦合。
定義依賴注入容器
public class BlocStore { typealias StoreLock = RecursiveLock typealias StoreTable = [String: BlocTable] private let lock: StoreLock = StoreLock() private lazy var storeTable: StoreTable = [:]} extension BlocStore { public func register<Service>(service: Service.Type = Service.self, to: Bloc.Type) { let key = "/(service)" lock.lock() defer { lock.unlock() } serviceTable[key] = to } @discardableResult public func optional<Service>(service: Service.Type = Service.self) -> Service? { let key = "/(service)" lock.lock() defer { lock.unlock() } let service = resolve(bloc) return s }} // Bind and unbindextension BlocStore { public func bindBloc(bloc: Bloc) { } public func unbindBloc<T: Bloc>(_ blocType: T.Type) { }} // BlocLifeCycleextension BlocStore { func onStart(bloc: Bloc?) { bloc?.onStart() } func onPause(bloc: Bloc?) { bloc?.onPause() } func onResume(bloc: Bloc?) { bloc?.onResume() } func onStop(bloc: Bloc?) { bloc?.onStop() }}
組件注冊
// 業務方根據業務邏輯可以注入不同的實現register(service: XXXProtocol.self, to: ABloc.self) // A業務形態register(service: XXXProtocol.self, to: BBloc.self) // B業務形態
組件解析
let s: XXXProtocol = store.optional()
scope分為頁面級和業務級兩種scope:
class VDScope { public static let core = "store.core.scope" public static let biz = "store.biz.scope"}
定義 Scope 管理來管理模塊的生命周期:
public class BlocStore { typealias ScopeTable = [String: String] ... func bizTypeDidChanged() { // 銷毀上一個bizscope下所有模塊 xxxx // 初始化新bizscope下模塊 xxx }}
這樣,新人進入開發時無需關注當前業務形態或業務形態切換的問題,達到快速上手的目的。
在開發資源和測試資源不變的情況下,業務范圍擴大了,我們該如何保障吞吐速度和質量的穩定呢?
圖片
我們可以將策略分為三個階段:
對于核心流程添加全鏈路日志,如果發現不符合預期的數據則直接拋出異常。
同時進行技術埋點上報。如果是對于核心流程的修改,強制添加AB降級方案。
有些bug非常隱蔽,在用戶體驗上可能沒有任何差異,但內部流程或數據可能已經發生異常。
對于類似問題,測試根本無法發現。導致此類問題流入線上的風險。我們可以通過添加監控和告警,讓我們及時發現問題。
我們可以通過添加監控和告警,讓我們及時發現問題。
具體實施方案:
首先,我們對通用詳情頁里核心流程添加了全鏈路日志,并為日志服務添加了兩項額外能力:
如果發現日志類型為Error,內部自動觸發DEBUG彈窗提醒,并上報技術埋點,達到對線上穩定性的監控。
圖片
同時,搭建離在線數據報表和異常告警,進一步保障穩定性。
至此,搭建了通用詳情頁從發現問題到定向拉取再到快速定位的閉環。
本文鏈接:http://www.tebozhan.com/showinfo-26-100716-0.html通用詳情頁的打造,你學會了嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 使用 Docker 搭建 NodeJS 開發環境是一種什么體驗?
下一篇: 一文搞懂 @Async 注解原理