通用詳情頁的打造,你學(xué)會了嗎?
背景介紹
圖片
大家都知道,詳情頁承載了站內(nèi)的核心流量。它的量級到底有多大呢?
我們來看一下,日均播放次數(shù)數(shù)億次,這么大的流量,其重要程度可想而知。
在這樣一個頁面,每一個功能都是大量業(yè)務(wù)的匯總點(diǎn)。
作為用戶核心消費(fèi)場景,詳情頁不僅需要承接各種業(yè)務(wù)的轉(zhuǎn)化,還要負(fù)責(zé)展示各業(yè)務(wù)在播放頁的功能。
可以說,播放頁的代碼復(fù)雜度屬于客戶端最高的代碼之一,這不僅因?yàn)椴シ彭摫旧淼墓δ軓?fù)雜,還因?yàn)樗枰诤洗罅客獠繕I(yè)務(wù)功能。
復(fù)雜的功能自然會帶來較高的代碼復(fù)雜度,而高代碼復(fù)雜度往往意味著高代碼維護(hù)成本。
明確需求
圖片
我們來看一下沒有做這個項(xiàng)目之前的狀態(tài)。如圖所示,他們分別為三個業(yè)務(wù)團(tuán)隊(duì)各自維護(hù)。頁面間相互獨(dú)立。能力無法復(fù)用。
圖片
通過這個項(xiàng)目,我們要將他們?nèi)诤铣闪艘粋€頁面。產(chǎn)品的訴求就是將他們?nèi)诤蠟橐粋€,來達(dá)到多業(yè)務(wù)形態(tài)統(tǒng)一的目標(biāo)。
圖片
但是,這三個詳情頁并不像產(chǎn)品想象的那么簡單。
每個業(yè)務(wù)都有自己的特殊形態(tài),如大型活動態(tài)、主客態(tài)、播單態(tài)、PUGV/OGV態(tài)等一系列業(yè)務(wù)形態(tài)。
每種形態(tài)都有自己的特殊邏輯,而且這些業(yè)務(wù)形態(tài)間還可以互相切換。
需求分析
為了更好地達(dá)成目標(biāo),我們需要進(jìn)行如下思考:
- 從業(yè)務(wù)角度:
要解決多業(yè)務(wù)形態(tài)不統(tǒng)一的問題。例如,產(chǎn)品既想要UGC大型活動的能力,又想要OGV的多視角功能。
但這兩個能力在之前分別是兩個業(yè)務(wù)團(tuán)隊(duì)各自開發(fā)的,無法復(fù)用,產(chǎn)品在業(yè)務(wù)選擇上無法兼得。
- 從效率角度:
要解決迭代方式不統(tǒng)一的問題。例如,進(jìn)度條體驗(yàn)優(yōu)化需求,產(chǎn)品在給UGC團(tuán)隊(duì)提需求的同時,還要復(fù)制一份給OGV團(tuán)隊(duì)。
兩個業(yè)務(wù)方的開發(fā)和測試都需要進(jìn)入這個項(xiàng)目,并且雙方的開發(fā)進(jìn)度和排期可能不一致。如果產(chǎn)品強(qiáng)烈要求同一版本上線,還需要協(xié)調(diào)各方資源。
- 從質(zhì)量角度:
要解決如何保障穩(wěn)定性的問題。例如,多團(tuán)隊(duì)協(xié)作,之前都是組內(nèi)同事協(xié)作開發(fā),現(xiàn)在融入了兩個新的業(yè)務(wù)團(tuán)隊(duì),我們該如何保障穩(wěn)定性。
- 從團(tuán)隊(duì)角度:
要解決如何讓新人快速上手的問題。正常情況下,新人想要進(jìn)入開發(fā)必須對這個系統(tǒng)足夠了解后才行。
更何況現(xiàn)在變成了三個業(yè)務(wù)融合的頁面。有沒有一種手段,讓新人無需關(guān)心復(fù)雜的業(yè)務(wù)形態(tài)和業(yè)務(wù)邏輯,只需要關(guān)注自己的需求?
具體方案
針對以上問題,我們可以總結(jié)出通用詳情頁框架必須滿足以上三點(diǎn),分別為:復(fù)用性,靈活性,穩(wěn)定性
圖片
接下來我們繼續(xù)對多業(yè)務(wù)形態(tài)進(jìn)行分析。
首先我們從橫向上進(jìn)行拆解,通過對比,我們可以發(fā)現(xiàn)
多業(yè)務(wù)形態(tài)間其實(shí)有很多的相同模塊。如互動,彈幕發(fā)送框,相關(guān)推薦等。
從縱向上進(jìn)行拆解,我們也可以發(fā)現(xiàn)很多相同模塊,如彈窗管理器,主題組件,轉(zhuǎn)場組件等。
那么從橫向和縱向上我們發(fā)現(xiàn),多種業(yè)務(wù)形態(tài)間其實(shí)有很多可以復(fù)用的能力。
圖片
基于前面的思考,我們設(shè)計(jì)了一套通用詳情頁的框架。將其分為三層:
- 業(yè)務(wù)層:將業(yè)務(wù)模塊分為兩類,能夠在多業(yè)務(wù)間復(fù)用的模塊抽象到通用業(yè)務(wù),業(yè)務(wù)獨(dú)有模塊則由各業(yè)務(wù)自行負(fù)責(zé)。
- 組件層:抽象出各種通用組件,業(yè)務(wù)方可自由選取和組裝。
- 框架層:抽象生命周期管理、數(shù)據(jù)管理等核心邏輯,以此來保證整個詳情頁的穩(wěn)定性。
這樣我們就初步解決了復(fù)用性的問題,但是隨之而來的就是靈活性問題。
圖片
我們以實(shí)際場景為例,相關(guān)推薦模塊在課堂態(tài)不展示,但是在ugc和ogv下需要展示,另外他的點(diǎn)擊事件在ugc和ogv下還會出現(xiàn)差異。
同時相關(guān)推薦模塊還強(qiáng)依賴簡介模塊。因?yàn)楹喗槟K也是一個通用組件,業(yè)務(wù)方可以自由替換。
如果哪天業(yè)務(wù)方替換了了簡介模塊,那相關(guān)推薦模塊將無法正常運(yùn)行。
從相關(guān)推薦這個例子我們可以得出如果想讓業(yè)務(wù)模塊復(fù)用,必須滿足兩個條件。
- 支持業(yè)務(wù)異化,即允許業(yè)務(wù)能插入自定義邏輯,否則現(xiàn)在抽象的通用模塊在迭代的過程一定會變成非通用,或者里面摻雜各種if else邏輯來支持異化。
- 必須保證模塊間相互獨(dú)立,因?yàn)樗袠I(yè)務(wù)邏輯在此框架下都變成了模塊,模塊是可以由業(yè)務(wù)方自由選擇的。
圖片
引入依賴注入
因此,我們需要在流程和模塊中加入依賴注入的能力,用于業(yè)務(wù)方實(shí)現(xiàn)差異化邏輯。
業(yè)務(wù)方可自行插入自己的業(yè)務(wù)邏輯,并選擇或替換業(yè)務(wù)模塊。來解決模塊間的耦合。
定義依賴注入容器
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 unbind
extension BlocStore {
public func bindBloc(bloc: Bloc) {
}
public func unbindBloc<T: Bloc>(_ blocType: T.Type) {
}
}
// BlocLifeCycle
extension 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()
}
}
組件注冊
// 業(yè)務(wù)方根據(jù)業(yè)務(wù)邏輯可以注入不同的實(shí)現(xiàn)
register(service: XXXProtocol.self, to: ABloc.self) // A業(yè)務(wù)形態(tài)
register(service: XXXProtocol.self, to: BBloc.self) // B業(yè)務(wù)形態(tài)
組件解析
let s: XXXProtocol = store.optional()
引入scope
scope分為頁面級和業(yè)務(wù)級兩種scope:
class VDScope {
public static let core = "store.core.scope"
public static let biz = "store.biz.scope"
}
定義 Scope 管理來管理模塊的生命周期:
- Page scope的生命周期與頁面保持一致,Biz scope與業(yè)務(wù)形態(tài)的生命周期保持一致。
- 即在頁面形態(tài)發(fā)生變化時,框架層會自動將bizscope下的所有模塊進(jìn)行銷毀。
public class BlocStore {
typealias ScopeTable = [String: String]
...
func bizTypeDidChanged() {
// 銷毀上一個bizscope下所有模塊
xxxx
// 初始化新bizscope下模塊
xxx
}
}
這樣,新人進(jìn)入開發(fā)時無需關(guān)注當(dāng)前業(yè)務(wù)形態(tài)或業(yè)務(wù)形態(tài)切換的問題,達(dá)到快速上手的目的。
如何保障吞吐速度和質(zhì)量穩(wěn)定?
在開發(fā)資源和測試資源不變的情況下,業(yè)務(wù)范圍擴(kuò)大了,我們該如何保障吞吐速度和質(zhì)量的穩(wěn)定呢?
圖片
我們可以將策略分為三個階段:
1.開發(fā)階段:
對于核心流程添加全鏈路日志,如果發(fā)現(xiàn)不符合預(yù)期的數(shù)據(jù)則直接拋出異常。
同時進(jìn)行技術(shù)埋點(diǎn)上報。如果是對于核心流程的修改,強(qiáng)制添加AB降級方案。
2.測試階段:
有些bug非常隱蔽,在用戶體驗(yàn)上可能沒有任何差異,但內(nèi)部流程或數(shù)據(jù)可能已經(jīng)發(fā)生異常。
對于類似問題,測試根本無法發(fā)現(xiàn)。導(dǎo)致此類問題流入線上的風(fēng)險。我們可以通過添加監(jiān)控和告警,讓我們及時發(fā)現(xiàn)問題。
3.灰度/線上階段:
我們可以通過添加監(jiān)控和告警,讓我們及時發(fā)現(xiàn)問題。
具體實(shí)施方案:
首先,我們對通用詳情頁里核心流程添加了全鏈路日志,并為日志服務(wù)添加了兩項(xiàng)額外能力:
如果發(fā)現(xiàn)日志類型為Error,內(nèi)部自動觸發(fā)DEBUG彈窗提醒,并上報技術(shù)埋點(diǎn),達(dá)到對線上穩(wěn)定性的監(jiān)控。
圖片
同時,搭建離在線數(shù)據(jù)報表和異常告警,進(jìn)一步保障穩(wěn)定性。
至此,搭建了通用詳情頁從發(fā)現(xiàn)問題到定向拉取再到快速定位的閉環(huán)。