日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
B站大型開播平臺(tái)重構(gòu)

1.背景

"凡事預(yù)則立,不預(yù)則廢。"

東昌網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,東昌網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為東昌上千多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的東昌做網(wǎng)站的公司定做!

——《禮記·中庸》

在文章的開頭,我們可以先來了解一下直播業(yè)務(wù)的大致業(yè)務(wù)架構(gòu)。將直播業(yè)務(wù)簡單分為兩大類場景"看播"、"開播",前者主要面向C端觀看用戶,后者主要面向B端開播主播。主播通過"開播工具"的開播產(chǎn)品功能,經(jīng)由"開播平臺(tái)"完成一系列開播動(dòng)作,最后將媒體信息采集推送到多媒體服務(wù)器,C端觀看用戶就可以從CDN看到直播的視頻流內(nèi)容。

從數(shù)據(jù)流向來講,"開播"場景是產(chǎn)生數(shù)據(jù)和觸發(fā)關(guān)鍵事件的源頭。這些數(shù)據(jù)或事件會(huì)涉及多個(gè)領(lǐng)域,如安全合規(guī)信息、房間信息、主播信息、開播場次信息、安全審計(jì)信息、多媒體信息等。

打個(gè)不太準(zhǔn)確的比喻。開播系統(tǒng)對(duì)于直播平臺(tái)的重要性,等同于訂單系統(tǒng)對(duì)于交易平臺(tái)的重要性。開播工具作為播端功能入口,直接面向官方開播工具(直播姬、粉版大加號(hào)、三方工具如OBS開播)的用戶以及內(nèi)部平臺(tái)方的用戶(其他業(yè)務(wù)線、產(chǎn)品&運(yùn)營),對(duì)開播體驗(yàn)負(fù)責(zé)。開播平臺(tái)在其中的職責(zé),是向開播工具和其他平臺(tái)方提供開播相關(guān)的平臺(tái)化業(yè)務(wù)能力,如開關(guān)播、開通直播間、切換分區(qū)等。

同時(shí),開播平臺(tái)與同級(jí)的業(yè)務(wù)平臺(tái)一起協(xié)作,才能支撐起完整的開播工具產(chǎn)品能力,如語聊房業(yè)務(wù)需要開播工具管理平臺(tái)(開播工具類支持)、主播互動(dòng)平臺(tái)(主播互動(dòng)能力支持)、流媒體服務(wù)端共同參與才能完成,從不同的維度幫助開播工具生態(tài)完善化。

圖片

一些涉及到的業(yè)務(wù)/技術(shù)名詞,在此我們也做出列舉并做出簡單介紹:

名詞

名詞簡述

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)

DDD 是 Domain-Driven Design 的縮寫,是 Eric Evans 于 2004 年提出的一種軟件設(shè)計(jì)方法和理念。

其主要的思想是,利用確定的業(yè)務(wù)模型來指導(dǎo)業(yè)務(wù)與應(yīng)用的設(shè)計(jì)和實(shí)現(xiàn)。主張開發(fā)人員與業(yè)務(wù)人員持續(xù)地溝通和模型的持續(xù)迭代式演化,以保證業(yè)務(wù)模型與代碼實(shí)現(xiàn)的一致性,從而實(shí)現(xiàn)有效管理業(yè)務(wù)復(fù)雜度,優(yōu)化軟件設(shè)計(jì)的目的。

領(lǐng)域知識(shí)

(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)概念)指能準(zhǔn)確傳達(dá)業(yè)務(wù)規(guī)則的描述,也是領(lǐng)域中業(yè)務(wù)知識(shí)的集中體現(xiàn)。

理想狀態(tài)下,領(lǐng)域?qū)<液途幋a人員對(duì)業(yè)務(wù)的認(rèn)知應(yīng)該完全一致,就算不同的人寫代碼也應(yīng)該偏差不大。

領(lǐng)域事件(Domain Event)

領(lǐng)域事件,是在業(yè)務(wù)上真實(shí)發(fā)生的客觀事實(shí),這些事實(shí)對(duì)系統(tǒng)會(huì)產(chǎn)生關(guān)鍵影響,是觀察業(yè)務(wù)系統(tǒng)變化的關(guān)鍵點(diǎn)。對(duì)于開播而言,"房間已經(jīng)被主播主動(dòng)流轉(zhuǎn)為開播了"就是一個(gè)領(lǐng)域事件。

視頻云(直播)

一般指廣義的直播流媒體業(yè)務(wù),提供主播推流、觀眾拉流的基礎(chǔ)能力。

看播

一般指廣義的直播觀看業(yè)務(wù)域,涉及進(jìn)房、彈幕互動(dòng)、禮物打賞等業(yè)務(wù)場景,一般面向C端(觀眾)

直播姬

一般指移動(dòng)端APP"嗶哩嗶哩直播姬",以及Windows系統(tǒng)下的"嗶哩嗶哩直播姬"應(yīng)用。(注:嗶哩嗶哩粉版APP和Web頁面也提供了開播能力)

測試驅(qū)動(dòng)開發(fā)(TDD)

測試驅(qū)動(dòng)開發(fā)是一種軟件開發(fā)過程中的應(yīng)用方法,由極限編程倡導(dǎo),以其倡導(dǎo)先寫測試程序,然后編碼實(shí)現(xiàn)其功能得名。本文中主要涉及ATDD集成測試驅(qū)動(dòng)和UTDD單元測試驅(qū)動(dòng)。

戰(zhàn)略設(shè)計(jì)

戰(zhàn)略設(shè)計(jì)也稱為戰(zhàn)略建模,是指對(duì)業(yè)務(wù)進(jìn)行高層次的抽象和歸類。主要手段包括理清上下文和進(jìn)行子域的劃分。

戰(zhàn)術(shù)設(shè)計(jì)

戰(zhàn)術(shù)設(shè)計(jì)也稱為戰(zhàn)術(shù)建模,是指對(duì)特定上下文下的模型進(jìn)行詳細(xì)設(shè)計(jì)。我們對(duì)開播新的微服務(wù)中各個(gè)模塊職責(zé)的編排,就是戰(zhàn)術(shù)設(shè)計(jì)的一部分。

開播平臺(tái)/開播服務(wù)平臺(tái)

一般指狹義的后端業(yè)務(wù),即提供開播房間狀態(tài)流轉(zhuǎn)、直接對(duì)客戶端提供推流信息的服務(wù)端業(yè)務(wù)。提供如開關(guān)播接口、推流地址獲取的通用業(yè)務(wù)接口。

開播工具

一般指廣義的可進(jìn)行開播的業(yè)務(wù)場景,如直播姬、Web主播中心、粉APP開播等,相較于開播更偏向于端到端場景

開播

一般指廣義的開播業(yè)務(wù)域,涉及開播、關(guān)播等業(yè)務(wù)場景,一般面向B端(主播)

事件風(fēng)暴(Event Storming)

事件風(fēng)暴是一種捕獲行為需求的方法,類似傳統(tǒng)軟件的開發(fā)用例分析。所有人員(領(lǐng)域?qū)<液图夹g(shù)專家) 對(duì)業(yè)務(wù)行為進(jìn)行一次發(fā)散,并最終收斂達(dá)到業(yè)務(wù)的統(tǒng)一。

事件溯源(Event Sourcing)

事件溯源是一種用事件日志追溯狀態(tài)的方法,因此事件溯源的關(guān)鍵在于事件日志。對(duì)于開播而言只是借用了"溯源"這種思想,用于保證新舊開播鏈路的關(guān)鍵狀態(tài)完全一致。

SOP

Standard Operating Procedure,標(biāo)準(zhǔn)作業(yè)程序。本次重構(gòu)過程,發(fā)布、應(yīng)急處理、故障處理都使用此種方式進(jìn)行推進(jìn)。

room / room-service

PHP歷史服務(wù),本次被遷移的主角。眾多歷史業(yè)務(wù)在該服務(wù)中。

live-streaming / streaming

新開播微服務(wù),今后承載開播領(lǐng)域主要業(yè)務(wù)的落地實(shí)體。

app-blink

開播工具網(wǎng)關(guān)層微服務(wù),直接承載客戶端的請(qǐng)求。

1.1 現(xiàn)狀和挑戰(zhàn)

直播開播系統(tǒng),伴隨著B站直播的成長貫穿始終。

發(fā)展初期所有的直播業(yè)務(wù)基本都在一套php代碼里完成,包括開播部分。之后的直播高速發(fā)展中,很多模塊已經(jīng)順利完成遷移。

開播部分也嘗試過遷移,但是未能成功完成。還不太幸運(yùn)的出了比較嚴(yán)重的線上故障。(這給后面的再次重構(gòu)積累了寶貴的經(jīng)驗(yàn)。)

1.1.1 債務(wù)清單

  • 業(yè)務(wù)積累厚:最初的代碼大致是從2017年開始的,要問里面的門道究竟有多少,可能另起一篇文章也難以詳盡。
  • 代碼可讀性差:php是弱類型+動(dòng)態(tài)類型特點(diǎn),代碼可讀性方面有非常大的挑戰(zhàn)。同時(shí)因?yàn)樯婕暗娇缯Z言遷移,需要有機(jī)制能檢查兩邊邏輯和數(shù)據(jù)的一致性。
  • 開發(fā)模式陳舊:php代碼在整個(gè)開發(fā)架構(gòu)上,也是偏"事務(wù)腳本模式"。多個(gè)領(lǐng)域混雜在一起,互相耦合調(diào)用,解耦異常困難。
  • 質(zhì)量配套欠缺:單元測試和自動(dòng)化測試方面也比較缺乏。要想順利完成重構(gòu)遷移,這塊是重要的前置工作。
  • 技術(shù)棧滯后:php技術(shù)棧,已經(jīng)不符合公司的整個(gè)技術(shù)棧主路線。各種lib、中間件支持方面欠缺,急需技術(shù)棧升級(jí)。

1.1.2 遺留系統(tǒng)特征

業(yè)界對(duì)遺留系統(tǒng)的普遍定義中有4個(gè)關(guān)鍵字:舊、過時(shí)、重要、仍在使用。

圖片

事實(shí)并非完全如此:有些系統(tǒng)時(shí)間雖長,但如果一直堅(jiān)持現(xiàn)代化的開發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測試策略、DevOps 等方面都保持先進(jìn)性,這樣的系統(tǒng)就像陳年的老酒一樣,歷久彌香。而有些系統(tǒng)雖然剛剛開發(fā)完成,但如果在上述幾個(gè)方面都做得不好,我們也可以把它叫做遺留系統(tǒng)。遺留系統(tǒng)在維護(hù)成本、合規(guī)性、安全性、集成性等方面都會(huì)給企業(yè)造成巨大的負(fù)擔(dān),但同時(shí)也蘊(yùn)含著豐富的數(shù)據(jù)和業(yè)務(wù)資產(chǎn)。我們應(yīng)該對(duì)遺留系統(tǒng)進(jìn)行現(xiàn)代化,讓它重新煥發(fā)青春。

顯然在知曉了舊開播系統(tǒng)有諸多歷史債務(wù)后,我們可以認(rèn)為它確實(shí)是一個(gè)搖搖欲墜的遺留系統(tǒng)。而我們本次的目標(biāo),就是將開播平臺(tái)這個(gè)重要的遺留系統(tǒng)進(jìn)行重構(gòu),讓它"煥發(fā)新生",并讓他在可預(yù)見的未來中都維持現(xiàn)代化系統(tǒng)的標(biāo)準(zhǔn)。

1.2 安全生產(chǎn)

在開播系統(tǒng)的維護(hù)、迭代、演進(jìn)中,我們也致力于系統(tǒng)的"安全生產(chǎn)"問題:

  • 如何降低研發(fā)的業(yè)務(wù)認(rèn)知成本、溝通成本,降低復(fù)雜度,從而提高"卡車系數(shù)",保證團(tuán)隊(duì)內(nèi)部能保證形成快速backup?
  • 如何通過技術(shù)演進(jìn),增加開播系統(tǒng)的可拓展性/魯棒性/可測試性?
  • 遷移新系統(tǒng)時(shí),新老系統(tǒng)如何優(yōu)雅安全切換、過程中的新舊系統(tǒng)數(shù)據(jù)是否可以進(jìn)行白盒對(duì)比?

2.開播系統(tǒng)架構(gòu)演進(jìn)

每個(gè)士兵在上戰(zhàn)場前必須清楚的明白,他這場小小的戰(zhàn)斗在大局中起的作用?!{德 · L · 蒙哥馬利(英國)

2.1 審視:問題出在哪里?

在著手進(jìn)行改造升級(jí)之前,不妨先從整體業(yè)務(wù)的迭代流程和已有架構(gòu)中找到問題,以確定真正值得樹立的目標(biāo),避免陷入"只見樹木不見森林"的狹小視野中。

我們不難發(fā)現(xiàn),這個(gè)日積月累的遺留系統(tǒng)當(dāng)中,它的業(yè)務(wù)研發(fā)流程種種令人難以忽視的問題:業(yè)務(wù)知識(shí)、業(yè)務(wù)架構(gòu)的認(rèn)識(shí)遺失、產(chǎn)研語言的不統(tǒng)一等等。

2.1.1 業(yè)務(wù)知識(shí)與業(yè)務(wù)架構(gòu)的生命周期

開播域作為播端的核心業(yè)務(wù)域,由于其悠久的歷史和維護(hù)團(tuán)隊(duì)同學(xué)的變更,在幾經(jīng)周折后,領(lǐng)域知識(shí)已經(jīng)處于混沌狀態(tài)。這種情況下,顯然比起遺留代碼和不合理的實(shí)現(xiàn)邏輯而言,更大的bug可能最終會(huì)發(fā)生在人身上,也就是我們對(duì)業(yè)務(wù)知識(shí)本身的認(rèn)識(shí):對(duì)業(yè)務(wù)知識(shí)缺乏了解,往往是拖慢業(yè)務(wù)迭代甚至是釀成線上事故的罪魁禍?zhǔn)住?/p>

對(duì)于業(yè)務(wù)架構(gòu)的認(rèn)知遺失,則會(huì)導(dǎo)致業(yè)務(wù)域內(nèi)職責(zé)的混亂:“這個(gè)新增業(yè)務(wù)是否應(yīng)該由我們負(fù)責(zé)?”。落實(shí)到開發(fā)者身上就變成了應(yīng)用架構(gòu)的混亂:“這個(gè)業(yè)務(wù)我們到底應(yīng)該寫在哪個(gè)微服務(wù)里?”

最明顯最集中的問題會(huì)爆發(fā)在端到端用例中:戰(zhàn)略設(shè)計(jì)上一個(gè)實(shí)體上業(yè)務(wù)行為的不清晰往往代表著一個(gè)甚至多個(gè)端到端用例的認(rèn)知缺失,映射到戰(zhàn)術(shù)實(shí)現(xiàn)上就會(huì)演變成災(zāi)難性的"需求引入變更時(shí),未考慮到某個(gè)用戶用例",最終在上線前的驗(yàn)收環(huán)節(jié)甚至是上線后,發(fā)現(xiàn)這個(gè)需求的引入導(dǎo)致了bug的產(chǎn)生。

我們當(dāng)然可以把這種事故歸結(jié)為“歷史遺留問題”,但是對(duì)于功能的使用者而言,這種糟糕體驗(yàn)會(huì)直接讓平臺(tái)被貼上“不專業(yè)”的負(fù)面標(biāo)簽。對(duì)平臺(tái)本身而言,這種災(zāi)難性的錯(cuò)誤堆砌也只會(huì)讓系統(tǒng)不斷熵增,復(fù)雜程度愈發(fā)不可收拾,最終花費(fèi)在處理問題、歷史代碼考古上的人力一增再增缺無濟(jì)于事。

這部分無疑是開播重構(gòu)項(xiàng)目中,最迫切需要解決的問題。

2.1.2 描述語言不統(tǒng)一

在業(yè)務(wù)人員和產(chǎn)品的角度來看,"開播"這個(gè)用例往往和各端開發(fā)人員所說的"開播"又有著某種微妙的差別。業(yè)務(wù)視角下的開播,往往是用戶一次完整的開播體驗(yàn),比如,打開移動(dòng)直播姬,調(diào)整好各種用戶設(shè)置,點(diǎn)擊開播,最終看到自己的畫面被正確投放到b站直播間,并且可以完成后續(xù)和觀眾的互動(dòng)。

而在技術(shù)視角下的開播,"開播"是各執(zhí)行方的橫切面組成的:客戶端完成最直接的ui/ux互動(dòng)、直播服務(wù)端進(jìn)行用戶請(qǐng)求校驗(yàn)、視頻流和直播業(yè)務(wù)數(shù)據(jù)的協(xié)調(diào)、視頻云負(fù)責(zé)接收用戶的上行視頻流;每一方對(duì)"開播"的這個(gè)詞解釋就產(chǎn)生了差異:客戶端進(jìn)入到直播界面并點(diǎn)擊開播叫開播,服務(wù)端的開播接口被調(diào)用了也被視為開播,視頻流被推送到視頻云上行服務(wù)器的時(shí)候也可能被視為開播。

泛泛而談的話,各方的解釋都沒有太大問題,但是這樣的解釋無法確切指定它在業(yè)務(wù)里處于哪一部分,會(huì)造成什么結(jié)果。最終呈現(xiàn)在一位新進(jìn)入技術(shù)團(tuán)隊(duì)的同學(xué)的眼中可能是這樣的場景:

舉例

客服:主播反饋線上無法開播?!締栴}平臺(tái)】PC直播姬; 【一級(jí)分類】開播; 【二級(jí)分類】無法開播; 【問題描述】主播反饋進(jìn)入移動(dòng)直播姬開播界面后,點(diǎn)擊開播后,不能正常推流;

開發(fā)1:是不是開播了多次?

客服:不是,主播開播了一次

開發(fā)1:那可以讓用戶重試

開發(fā)2:是不是視頻云推流服務(wù)出了問題?@視頻云

客服:用戶已經(jīng)重試了,還是不能正常開播(其實(shí)是在另一臺(tái)設(shè)備上已經(jīng)推流了,還在嘗試使用其他設(shè)備推流)

開發(fā)3:視頻云看到用戶推流是正常的 (推流監(jiān)控圖)

開發(fā)1:哦,原來是重復(fù)開播了

假設(shè)我是一位團(tuán)隊(duì)新成員,在看到最終輸出的"重復(fù)開播"結(jié)論之前,得到的都是點(diǎn)狀的信息,沒有完整的用例以供參考,難以理解線上問題的癥結(jié)在何處。如果這個(gè)時(shí)候甚至沒有文檔來描述開播領(lǐng)域相關(guān)業(yè)務(wù),或者是開播流程的場景快照,那更是一場新人的災(zāi)難——可能需要專門請(qǐng)教團(tuán)隊(duì)中熟悉開播領(lǐng)域的資深開發(fā)為他進(jìn)行講解才能瞥見開播業(yè)務(wù)的一隅,且授課效果還要取決于講述人的結(jié)構(gòu)化敘述能力,這是我們從效率考量上不愿意見到的。

可以舉一個(gè)貼近實(shí)際開發(fā)人員的例子,"請(qǐng)教了3位同事才知道了開播記錄是怎么產(chǎn)生的"、"請(qǐng)教了3位同事才本地構(gòu)建成功",諸如此類的尷尬在日常工作中屢見不鮮的,實(shí)際上這類問題只會(huì)對(duì)程序員了解業(yè)務(wù)和編碼的積極性,以及商業(yè)化產(chǎn)品開發(fā)落地的效率起反作用。

2.1.3 對(duì)程序員的"人文關(guān)懷"

一個(gè)貼近實(shí)際開發(fā)人員的例子,"請(qǐng)教了3位同事才知道了開播記錄是怎么產(chǎn)生的"、"請(qǐng)教了3位同事才本地構(gòu)建歷史服務(wù)成功",諸如此類的尷尬在日常工作中屢見不鮮的,實(shí)際上這類問題只會(huì)對(duì)程序員了解業(yè)務(wù)和編碼的積極性、產(chǎn)品開發(fā)落地的效率起反作用。

要解決這種效率抑或積極性問題,還是需要解決根源上的"知識(shí)共享"問題。

 2.2 引入領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

在前文敘述問題的時(shí)候,熟悉的讀者可能就已經(jīng)想到了某個(gè)熱度經(jīng)久不衰的架構(gòu)思想:領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。是的,我們?cè)陂_播平臺(tái)的重構(gòu)中決定使用這種方式來解決現(xiàn)有的諸多痛點(diǎn)。依靠領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的設(shè)計(jì)思想,通過事件風(fēng)暴建立領(lǐng)域模型,合理劃分領(lǐng)域邏輯和物理邊界,建立與現(xiàn)實(shí)世界相映射的領(lǐng)域?qū)ο蠛头?wù)架構(gòu)圖,定義符合DDD分層架構(gòu)思想的代碼結(jié)構(gòu)模型,保證業(yè)務(wù)模型與代碼模型的一致性。

相對(duì)的,對(duì)于最終的效果,也是可以預(yù)期到的:

  • 統(tǒng)一業(yè)務(wù)模型和代碼模型,領(lǐng)域知識(shí)全體共享,提升協(xié)助效率;
  • 通過邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡單化,設(shè)計(jì)出清晰的領(lǐng)域和應(yīng)用邊界,實(shí)現(xiàn)業(yè)務(wù)和技術(shù)統(tǒng)一的架構(gòu)演進(jìn),提高人效,拒絕一加功能排期一個(gè)月;
  • 通過職責(zé)劃分合理的職責(zé)邊界,降低架構(gòu)腐敗速度;

2.3 領(lǐng)域驅(qū)動(dòng)視角看開播

回到"開播"這個(gè)待解決的問題域本身,對(duì)開播業(yè)務(wù)中最核心的"開播"用例,核心的業(yè)務(wù)問題包括以下幾點(diǎn)需要明確:

  • 如何確定房間是否可以開始直播。
  • 如何讓房間開始直播。
  • 如何通知外界房間已經(jīng)開始直播。

也有一些非功能性的考慮:

  • 如何使技術(shù)實(shí)現(xiàn)貼近現(xiàn)實(shí)中的業(yè)務(wù)原貌,從而降低認(rèn)知成本
  • 如何提高我們對(duì)業(yè)務(wù)和產(chǎn)品的認(rèn)知程度和積極性
  • 如何提高開播功能的魯棒性和性能

首先,我們可以采用事件驅(qū)動(dòng)開發(fā)方法,結(jié)合領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的事件風(fēng)暴方法論,來梳理開播用例中的關(guān)鍵事件和參與者:

事件風(fēng)暴的核心流程就是由用戶執(zhí)行了命令,從而產(chǎn)生了事件?;谶@個(gè)事件的結(jié)果,與之前相同或是其他的用戶會(huì)執(zhí)行另一個(gè)命令,產(chǎn)生新類型的事件,以此類推。而順序是按照業(yè)務(wù)邏輯而定的。所以我們?cè)谡黹_播涉及的時(shí)間風(fēng)暴時(shí),工作流如下:

  1. 確定用例的發(fā)起者,即主語。在開播場景中,可以是主播或者運(yùn)營。
  2. 確定主語的動(dòng)作,比如"開啟直播"。
  3. 確定動(dòng)作的流程中涉及的命令以及執(zhí)行命令產(chǎn)生的事件、后果,例如"新場次已被主播創(chuàng)建"。
  4. 補(bǔ)充流程中涉及的業(yè)務(wù)知識(shí),例如"房間"、各種各樣的"檢查規(guī)則"以及外部系統(tǒng)。
  5. 當(dāng)一個(gè)完整的業(yè)務(wù)流程通過上述方式寫完之后,對(duì)于每個(gè)用戶,命令,事件進(jìn)行組合,就能獲得聚合,用事件風(fēng)暴的描述開播場次創(chuàng)建就是"主播在場次聚合上進(jìn)行了創(chuàng)建場次操作,導(dǎo)致了新場次創(chuàng)建事件",此事件發(fā)生后,用戶會(huì)在房間聚合上執(zhí)行房間開播狀態(tài)流轉(zhuǎn)操作的新命令。

圖片

事件風(fēng)暴中"定義領(lǐng)域模型"是最重要的一步,這一步需要了解實(shí)際業(yè)務(wù)形態(tài)后團(tuán)隊(duì)內(nèi)大量討論,從而達(dá)成共識(shí)。在這個(gè)階段中我們提煉了諸多的業(yè)務(wù)表現(xiàn)并且屏蔽技術(shù)實(shí)現(xiàn)細(xì)節(jié),提取出了關(guān)鍵的實(shí)體、值對(duì)象、聚合根。緊接著就可以著手對(duì)事件風(fēng)暴中的概念進(jìn)行進(jìn)一步的歸納。

通過以上步驟,我們可以清晰地梳理出開播用例中的關(guān)鍵事件和參與者,為后續(xù)的設(shè)計(jì)和開發(fā)工作奠定基礎(chǔ)。

業(yè)務(wù)描述拆解成主語和動(dòng)詞的形式后,可以發(fā)現(xiàn)"房間"和"場次"是這個(gè)問題域的兩個(gè)主要元素。在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,需要將這兩個(gè)支撐域進(jìn)行集成,最終形成"開播域"的基本解決方案。為了確保開播業(yè)務(wù)流程的完整性,還需要將"安全管控"、"分區(qū)"、"賬號(hào)"等子域或外部系統(tǒng)的知識(shí)參與其中,并將其作為業(yè)務(wù)規(guī)則和值對(duì)象等等的形式進(jìn)行表達(dá)。

圖片

根據(jù)近一年的業(yè)務(wù)現(xiàn)狀,我們參考領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)模式,進(jìn)行了領(lǐng)域上下文的劃分:

子域

能力

業(yè)務(wù)子域重點(diǎn)

[核心域] 開播域

集成開播相關(guān)的所有上下文,形成對(duì)開播場景下業(yè)務(wù)用例的實(shí)現(xiàn)方案。如開播、關(guān)播等。

開播 = 直播場次被創(chuàng)建 + 房間狀態(tài)變?yōu)殚_播 + 開播領(lǐng)域事件被發(fā)出

安全管控 = 開播常態(tài)化管控能力 + 應(yīng)急管控能力,如僅允許官方直播間開播

[通用域] 房間域

基礎(chǔ)業(yè)務(wù)能力,僅處理播端房間基礎(chǔ)業(yè)務(wù),如變更房間開播狀態(tài)。

房間狀態(tài)的增刪改查

發(fā)送開關(guān)播領(lǐng)域事件

[通用域] 場次域

基礎(chǔ)業(yè)務(wù)能力,僅處理開播領(lǐng)域產(chǎn)生的開關(guān)播場次信息

開關(guān)播場次信息、子場次信息(同一場直播下不同分區(qū))

[支撐子域] 房間管理域

保障房間域和開播域之間集成業(yè)務(wù)的業(yè)務(wù)完整性,如房間的推流管理。

房間關(guān)鍵依賴對(duì)賬:保障新開通房間存在視頻云上行推流地址

直播CQRS對(duì)賬:保障播端、觀看端的房間基礎(chǔ)信息一致

視頻云流管理能力封裝

[支撐子域] 開播安全管控域

開通直播間、預(yù)開播、開播、切換分區(qū)、推流管控策略

提供可配置、產(chǎn)品化的常態(tài)化策略管控能力,如是否允許某些地區(qū)開播,某分區(qū)是否需要延遲

必須具備快速應(yīng)對(duì)重要事件的能力。

外部域

視頻云管理

視頻云所屬領(lǐng)域,提供上行推流相關(guān)能力。開播主要使用上行推流管理、查詢上行推流地址

直播分區(qū)

看端所屬領(lǐng)域,提供分區(qū)基礎(chǔ)知識(shí)。開播僅使用其查詢分區(qū)元信息,作為開播安全管控的輸入

賬號(hào)上下文

主站賬號(hào)所屬領(lǐng)域,提供實(shí)名、等級(jí)相關(guān)知識(shí)。開播需要使用賬號(hào)信息、粉絲數(shù)等,作為開播安全管控的輸入

明確領(lǐng)域上下文和解決域的劃分后,緊接著就可以進(jìn)行DDD指導(dǎo)下的解決域戰(zhàn)術(shù)落地了。

領(lǐng)域劃分落實(shí)到戰(zhàn)術(shù)上的一個(gè)方案就是微服務(wù),微服務(wù)將直接作為開播域這個(gè)核心域與其他子域的實(shí)際界限。

在下文中,我們會(huì)講述如何將當(dāng)前的PHP遺留服務(wù),這個(gè)不滿足領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的開發(fā)架構(gòu),演進(jìn)為受領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)的、貼合業(yè)務(wù)的、使用Golang搭建的整潔架構(gòu)。

3.開發(fā)架構(gòu)

設(shè)計(jì)不只是感觀,設(shè)計(jì)就是產(chǎn)品的工作方式?!返俜颉滩妓?/p>

開門見山地講,經(jīng)過了多年積累后的舊版開播的遺留代碼是工程導(dǎo)向的,里面不乏炫技的代碼、大段冗長而缺乏業(yè)務(wù)注釋的代碼,這讓"開播"這個(gè)重要的業(yè)務(wù)領(lǐng)域在技術(shù)實(shí)現(xiàn)上,與業(yè)務(wù)方的實(shí)際描述漸行漸遠(yuǎn)。每當(dāng)我們提及一些業(yè)務(wù)場景,都需要絞盡腦汁才能回想起這個(gè)場景到底與哪些代碼有一些聯(lián)系。這樣的開發(fā)架構(gòu)和技術(shù)實(shí)現(xiàn)方式,不論對(duì)團(tuán)隊(duì)的知識(shí)共享還是對(duì)業(yè)務(wù)的正常迭代,都是一筆不可忽視的成本。

同時(shí),由于陳年代碼經(jīng)手多代程序員,導(dǎo)致代碼風(fēng)格不統(tǒng)一、領(lǐng)域邏輯和UI邏輯耦合的情況幾乎隨處可見,DAO代碼更是可能隨時(shí)出現(xiàn)在各個(gè)層次,這樣的耦合對(duì)可拓展性和可測試性都帶來了不小的麻煩。

本次重構(gòu)在戰(zhàn)術(shù)落地層面所面臨的挑戰(zhàn),就是如何在保證業(yè)務(wù)邏輯幾乎不變的情況下,讓業(yè)務(wù)描述與代碼實(shí)現(xiàn)更貼切、認(rèn)知負(fù)荷更低從而加強(qiáng)業(yè)務(wù)知識(shí)的地位,以及如何優(yōu)雅地解耦原本雜亂耦合的各層次代碼,讓他們變得整潔、可測試、可拓展。

3.1 設(shè)計(jì)模式

我們不妨先管中窺豹,看一個(gè)簡化版的新舊版本開播的時(shí)序圖對(duì)比:

圖片

圖片

很顯然,前者的描述充斥著純技術(shù)屬性的描述,大部分篇幅集中于諸如area_id、uid之類的屬性,難以直接和實(shí)際業(yè)務(wù)中的描述對(duì)應(yīng)上。

而后者的描述則是有業(yè)務(wù)上的主客體描述的,如"房間是否屬于該用戶"、"分區(qū)是否允許該房間開播"。在代碼的編排描述上,很容易就可以看出,后者的可理解性要比前者高出一截,這便引出了下文要討論的話題:新舊版本開播服務(wù)的設(shè)計(jì)模式。

3.1.1 舊版設(shè)計(jì)模式:事務(wù)腳本

事務(wù)腳本模式也叫做面條代碼或者膠水代碼;它有一些顯著的特點(diǎn):面向過程,易于編寫,難以應(yīng)對(duì)變更,復(fù)雜事務(wù)腳本可讀性低&可維護(hù)性低。顯然,舊版php開播代碼在多年缺乏系統(tǒng)性維護(hù)的業(yè)務(wù)迭代后,已經(jīng)幾乎退化為這樣的模式。

根據(jù)Fowler在PoEAA中對(duì)事務(wù)腳本的描述,我們用上文的舊版開播進(jìn)行分析。初次讀這段代碼的體驗(yàn),可能是如下的:

  1. 從表示層/服務(wù)層獲得輸入(開播請(qǐng)求的一些參數(shù),比如room_id、uid)
  2. 中間有大量的過程是用來做單純的獲取某一條數(shù)據(jù)(area_info分區(qū)信息)
  3. 獲取對(duì)這些獲取的單一數(shù)據(jù)進(jìn)行某些字段的判斷,或者多個(gè)單條數(shù)據(jù)聯(lián)合判斷(如,檢查房間開播狀態(tài)、檢查分區(qū)狀態(tài)是否為online)
  4. 之后調(diào)用其他系統(tǒng)或者存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫(多次更新房間的多條信息,live_start_time/area_id)
  5. 過程中,不斷將單條操作后新增的數(shù)據(jù)合并到響應(yīng)值中

開播這個(gè)動(dòng)作,在舊版代碼中乍一看,是一個(gè)過程驅(qū)動(dòng),由許多僅有技術(shù)含義的動(dòng)作完成的純技術(shù)操作,缺乏了對(duì)業(yè)務(wù)的基本感知和描述。這樣的模式,對(duì)業(yè)務(wù)中的場景業(yè)務(wù)歸納能力較弱,當(dāng)我們提到某個(gè)場景時(shí),往往需要把這些生硬的代碼在腦海中轉(zhuǎn)譯一次,才能對(duì)應(yīng)上業(yè)務(wù)方的實(shí)際描述。

當(dāng)我們擁有了更多動(dòng)作時(shí),就會(huì)有若干過程需要做相似的動(dòng)作,通常就要使多個(gè)過程中包含某些相同的代碼,這些類似的副本會(huì)讓應(yīng)用程序變成一張極度雜亂無章的網(wǎng)。

換一個(gè)角度,從OOP的角度上看該模式,實(shí)體的概念并不能完全表現(xiàn),甚至只是充當(dāng)了業(yè)務(wù)邏輯層和數(shù)據(jù)訪問層之間的輔助角色,只空有屬性,沒有行為。這樣實(shí)體在 業(yè)務(wù)行為上難以和代碼實(shí)現(xiàn) 對(duì)應(yīng),更難以復(fù)用。

3.1.2 新版設(shè)計(jì)模式:領(lǐng)域模型

反之,對(duì)比起原來的事務(wù)腳本模式,我們的新服務(wù)中,包含有多個(gè)有血有肉的對(duì)象,比如房間和賬號(hào)。

對(duì)于應(yīng)用服務(wù)需要完成的開播用例而言,相比起純過程的各個(gè)字段和子過程的串聯(lián),更關(guān)心每一個(gè)對(duì)象應(yīng)該做出什么行為。

圖片

圖片

3.2 戰(zhàn)術(shù)設(shè)計(jì)

3.2.1 戰(zhàn)術(shù)設(shè)計(jì)的思考:引入六邊形架構(gòu)

既然是遺留系統(tǒng)現(xiàn)代化演進(jìn),我們不妨先提一些工程質(zhì)量方面的提升預(yù)期:

業(yè)務(wù)領(lǐng)域的邊界更加清晰、更好的可擴(kuò)展性、對(duì)測試的友好支持、更容易實(shí)施DDD...

看到既定目標(biāo),再結(jié)合領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)的前情提要,相信對(duì)此熟悉的讀者已經(jīng)會(huì)心一笑了,解決方案呼之欲出:六邊形架構(gòu)。

3.2.2 領(lǐng)域模型與六邊形架構(gòu)

怎么寫開發(fā)架構(gòu)相對(duì)整潔、看起來就可測試的代碼?相信大家或多或少都了解過"六邊形架構(gòu)"、"整潔架構(gòu)"或者"洋蔥架構(gòu)"。我們?cè)賮砩晕?fù)習(xí)一下它的定義:

六邊形架構(gòu),也被稱為端口和適配器架構(gòu)(Ports and Adapters Architecture),是由Alistair Cockburn于2005年首次提出的。這個(gè)架構(gòu)模式的主要目標(biāo)是將應(yīng)用程序的核心業(yè)務(wù)邏輯與外部依賴分離開來,從而提高可測試性、可維護(hù)性和可擴(kuò)展性。

在六邊形架構(gòu)中,應(yīng)用程序被劃分為以下幾個(gè)關(guān)鍵部分:

  • 應(yīng)用程序核心:這是應(yīng)用程序的主要業(yè)務(wù)邏輯,它包含了所有的用例和業(yè)務(wù)規(guī)則。核心不依賴于具體的外部組件或技術(shù),因此它是高度可測試的。
  • 端口:端口是定義應(yīng)用程序與外部依賴之間的接口。它們定義了應(yīng)用程序需要的功能,但不實(shí)現(xiàn)具體的實(shí)現(xiàn)細(xì)節(jié)。
  • 適配器:適配器是實(shí)際實(shí)現(xiàn)端口的組件,它們負(fù)責(zé)將外部依賴集成到應(yīng)用程序中。適配器將外部依賴的細(xì)節(jié)隱藏在內(nèi)部,以確保核心業(yè)務(wù)邏輯保持獨(dú)立性。

通過將應(yīng)用程序核心與外部依賴分離,六邊形架構(gòu)提供了以下優(yōu)勢(shì):

  • 可測試性:由于核心業(yè)務(wù)邏輯與外部依賴分離,開發(fā)人員可以輕松地編寫單元測試,而無需依賴外部資源。
  • 可維護(hù)性:應(yīng)用程序的核心業(yè)務(wù)邏輯保持簡單和獨(dú)立,因此更容易理解和維護(hù)。
  • 可擴(kuò)展性:通過添加新的端口和適配器,您可以輕松地?cái)U(kuò)展應(yīng)用程序,以滿足不斷變化的需求。

本次我們新搭建的開播平臺(tái),遵循了端口和適配器的架構(gòu)風(fēng)格,將服務(wù)拆分為了以下的層次:

  • Transporter Layer 外部請(qǐng)求適配器,適配外部用例
  • Data Source Adapters 內(nèi)部資源適配器,適配內(nèi)部資源(Repository、Infrastructure)
  • Application 應(yīng)用程序?qū)樱深I(lǐng)域邏輯為用例,如"用戶使用直播姬開播"
  • Domain 領(lǐng)域?qū)?,業(yè)務(wù)邏輯核心,眾多重要邏輯在這里實(shí)現(xiàn),如"房間狀態(tài)流轉(zhuǎn)為開播"

圖片

解決的問題

  • 在PHP的老設(shè)計(jì)中,開播接口的ui/領(lǐng)域內(nèi)業(yè)務(wù)邏輯耦合較重,大量客戶端參數(shù)校驗(yàn)、特定客戶端返回特定響應(yīng)的邏輯耦合在多個(gè)地方,可能是controller,也可能是service,甚至可能是dao層。這部分與領(lǐng)域知識(shí)本身無關(guān),在新版本的開播代碼中,需要與應(yīng)用程序?qū)雍皖I(lǐng)域?qū)痈綦x起來,保護(hù)后者的邏輯不受污染。
  • 歷史遺留代碼中,DAO耦合在代碼中,對(duì)業(yè)務(wù)邏輯本身的可拓展性和可測試性產(chǎn)生了阻礙;新版代碼中也需要將這部分解耦,以便未來技術(shù)演進(jìn)和單元測試的開展。

3.2.3 模塊設(shè)計(jì)

按照上文的開發(fā)架構(gòu)設(shè)計(jì),本次新開播服務(wù)的代碼分包結(jié)構(gòu)代碼實(shí)現(xiàn)如下。

圖片

因?yàn)镚olang本身OOP的鴨子類型特性和諸多原因,我們的編碼風(fēng)格顯得沒有那么嚴(yán)格,選擇了相對(duì)松散的代碼分包結(jié)構(gòu)。大致區(qū)分為了領(lǐng)域?qū)?、防腐層、?yīng)用程序?qū)?、倉儲(chǔ)/基礎(chǔ)設(shè)施層

Domain 領(lǐng)域?qū)?/h4>

作為領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)下的工程,我們的首要目標(biāo)就是保障領(lǐng)域邏輯的正確性。

最核心的領(lǐng)域?qū)?,svc/pkg/domain包含了領(lǐng)域服務(wù)和各個(gè)領(lǐng)域?qū)ο蟮膇nterface契約聲明和具體實(shí)現(xiàn)。開播涉及到的領(lǐng)域?qū)ο蠖荚谶@里集中實(shí)現(xiàn)。

在開播的戰(zhàn)略設(shè)計(jì)中,我們提到了幾個(gè)上下文,在這里會(huì)作為聚合或?qū)ο蟮姆绞竭M(jìn)行實(shí)現(xiàn),他們的關(guān)系如下:

圖片

Facade 防腐層

對(duì)應(yīng)設(shè)計(jì)中的Transporter Layer 外部請(qǐng)求適配器,適配外部用例:

用例上,開播和用于調(diào)試開播的請(qǐng)求,都需要在用例層面適配他們,所以自然需要適配器來適配他們的grpc請(qǐng)求,以及考慮到今后多種接口形式的接入( http or mq),如果之后grpc定義出現(xiàn)變更,或者新請(qǐng)求形式的接入,不會(huì)對(duì) Application 層的用例帶來滲透和影響。

設(shè)計(jì)原則

需要提供多組外部適配器,適配各種場景的開播請(qǐng)求(理論上可能是grpc/http/mq ...,本文僅限于直播姬開播的場景),并轉(zhuǎn)化為應(yīng)用程序?qū)涌山邮艿挠美?jí)別請(qǐng)求。并且作為防腐層,不應(yīng)該有過多業(yè)務(wù)邏輯,僅實(shí)現(xiàn)必要的特定端到端場景的UI邏輯。

  • 處于防腐層的適配器需要有自己的合法校驗(yàn)邏輯(必要的靜態(tài)參數(shù)檢查)。
  • 防腐層約定上層(controller的 grpc server)以及下層應(yīng)用的(application)的交互協(xié)議,開播中對(duì)應(yīng)開播的gRPC請(qǐng)求轉(zhuǎn)換為Application層可接受的請(qǐng)求。
  • 歷史邏輯中只與UI相關(guān)的邏輯也需要在這一層收斂。(etc.開播失敗情況下的端上提示、彈窗的相關(guān)返回值組裝)

對(duì)外契約:

  • 適配網(wǎng)關(guān)層開播接口的請(qǐng)求:需要有靜態(tài)參數(shù)檢查、將請(qǐng)求構(gòu)造為application層可接受的ACL請(qǐng)求。
  • 開播返回結(jié)構(gòu)體組裝:將application層開播用例的結(jié)果,根據(jù)端到端的UI邏輯,組裝成業(yè)務(wù)預(yù)期中的返回值(防止客戶端UI相關(guān)的邏輯滲透到下面的層次)

Application 應(yīng)用程序?qū)?/h4>

應(yīng)用層作為場景用例的主體部分,充當(dāng)了實(shí)體、聚合、領(lǐng)域服務(wù)的膠水層,將房間、場次、賬號(hào)的行為集成到一起,最終形成"直播姬開播"用例的業(yè)務(wù)邏輯。最終,每一個(gè)用例會(huì)對(duì)應(yīng)application的一個(gè)接口,如"直播姬開播"、"直播姬關(guān)播"、"后臺(tái)開播"、"后臺(tái)關(guān)播"等等,包裝成用例提供到外部。

Repository / Infrastructure 倉儲(chǔ)/ 基礎(chǔ)設(shè)施

對(duì)應(yīng)Data Source Adapters 內(nèi)部資源適配器。同樣的,六邊形架構(gòu)中,對(duì)下游依賴的約束也是依靠 接口與適配器 這一風(fēng)格進(jìn)行解耦和契約。

設(shè)計(jì)原則

  • 內(nèi)部資源適配器應(yīng)該依照上層契約實(shí)現(xiàn)
  • 內(nèi)部資源適配器實(shí)現(xiàn)的變更不會(huì)影響到聲明的契約本身,其交付的能力應(yīng)當(dāng)是不變的
  • 倉儲(chǔ) Repository,按照Domain層協(xié)商的倉儲(chǔ)能力進(jìn)行實(shí)現(xiàn),該層只對(duì)領(lǐng)域邏輯要求的倉儲(chǔ)能力負(fù)責(zé),如"發(fā)布開播領(lǐng)域事件"
  • 基礎(chǔ)設(shè)施層 Infrastructure,領(lǐng)域無關(guān)的基礎(chǔ)設(shè)施,如分布式鎖、上報(bào)組件等

3.3 測試驅(qū)動(dòng)開發(fā) TDD

當(dāng)露營結(jié)束離開的時(shí)候,要打掃營地,讓它比你來的時(shí)候更干凈?!?童子軍原則,《97 Things Every Programmer Should Know》

3.3.1 動(dòng)機(jī)

從項(xiàng)目角度出發(fā),可以提供持續(xù)的項(xiàng)目進(jìn)度反饋。開播平臺(tái)重構(gòu)作為一個(gè)大型項(xiàng)目,需要從業(yè)務(wù)和項(xiàng)目的量化成一個(gè)個(gè)可操作的任務(wù)寫到 to-do list,然后使用測試驅(qū)動(dòng)編碼,可以在每一個(gè)預(yù)期用例完成后進(jìn)行標(biāo)記,那么我們的工作目標(biāo)將變得非常清晰,因?yàn)楣て?、待辦事項(xiàng)、難點(diǎn)都非常明確,可以在持續(xù)細(xì)微的反饋中有意識(shí)地做一些適當(dāng)?shù)恼{(diào)整,比如添加新的任務(wù),刪除冗余的測試;還有一點(diǎn)更加讓人振奮,可以知道大概什么時(shí)候可以完工,對(duì)開發(fā)進(jìn)度可以更精確的把握。

從工程角度出發(fā),可以確保代碼質(zhì)量,也保障重構(gòu)的安全性。一個(gè)軟件的自動(dòng)化測試,可以從內(nèi)部表達(dá)這個(gè)軟件的質(zhì)量,我們通常管它叫做內(nèi)建質(zhì)量(Build Quality In)。開發(fā)人員如果忽視編寫自動(dòng)化測試,就放棄了將質(zhì)量內(nèi)建到軟件(也就是自己證明自己質(zhì)量)的機(jī)會(huì),把質(zhì)量的控制完全托付給了測試人員。這種靠人力去保證質(zhì)量的方式,永遠(yuǎn)也不可能代表"技術(shù)先進(jìn)性"。在用例級(jí)別保障了內(nèi)建質(zhì)量后,倘若將來有一天需要重構(gòu),由于有全面的測試套件作為保障,開發(fā)人員可以放心地對(duì)代碼進(jìn)行優(yōu)化、改進(jìn)結(jié)構(gòu)或增加新功能,而不用擔(dān)心引入潛在的問題。

3.3.2 實(shí)踐

一句話來概括就是先設(shè)計(jì)用例,再寫代碼。

鑒于六邊形架構(gòu)符合 端口與適配器 風(fēng)格的契約,我們很容易知道:

  • 一個(gè)端口(interface)提供的能力是什么
  • 業(yè)務(wù)邏輯應(yīng)該使用什么端口(interface),他們應(yīng)該在業(yè)務(wù)中表現(xiàn)如何

那么以下的TDD工作流就應(yīng)該被遵守:

  1. 先明確interface的能力,定義所需的行為,并編寫可讀性良好的注釋文檔來聲明它們的作用;
  2. 根據(jù)上一步的契約,編寫interface的測試用例。
  3. 實(shí)現(xiàn)interface的業(yè)務(wù)邏輯,并實(shí)現(xiàn)接口以使其能夠通過。
  4. 業(yè)務(wù)邏輯測試,并使用上文編寫的用例進(jìn)行測試,驗(yàn)證預(yù)期行為是否在待測的interface中產(chǎn)生。
  5. 根據(jù)結(jié)果調(diào)整代碼,直到可以通過測試用例。

由于六邊形架構(gòu)中,接口與其實(shí)現(xiàn)天然存在接縫(seam),對(duì)于某個(gè)業(yè)務(wù)邏輯中對(duì)Repository甚至領(lǐng)域?qū)ο蟮那闆r,我們也可以輕松通過mock的方式進(jìn)行依賴處理。

以房間聚合的開播狀態(tài)流轉(zhuǎn)作為舉例:

圖片

第一步,我們根據(jù)"開播狀態(tài)流轉(zhuǎn)"這個(gè)領(lǐng)域?qū)ο蟮膭?dòng)作,進(jìn)行需求分析,得出該動(dòng)作的目的就是"將房間狀態(tài)流轉(zhuǎn)為開播中",一些關(guān)聯(lián)的知識(shí)就包括,"必須聲明開播時(shí)間"、"狀態(tài)流轉(zhuǎn)為開播的同時(shí)需要與一場直播綁定"、"開播的房間必須已經(jīng)選擇了分區(qū)"。明確需求后,從業(yè)務(wù)邏輯中得到想要的用例可以得到如下的用例:

  1. 完全符合預(yù)期,開播的動(dòng)作中包含所選的開播時(shí)間、場次、分區(qū),所以房間狀態(tài)可以流轉(zhuǎn)為開播
  2. 空操作,不合法輸入,失敗
  3. 沒有聲明開播時(shí)間,不合法輸入,失敗
  4. 沒有選擇分區(qū),不合法輸入,失敗
  5. 沒有綁定一場直播,不合法輸入,失敗
  6. Repository倉儲(chǔ)方法調(diào)用錯(cuò)誤,失敗

同時(shí)也可以注意到,在開播狀態(tài)流轉(zhuǎn)中,我們只關(guān)注依賴的Repository的倉儲(chǔ)方法是否失敗,而不關(guān)心它如何實(shí)現(xiàn)的、為何失敗的。因?yàn)檫@對(duì)于房間對(duì)象而言并不是職責(zé)范圍內(nèi)的知識(shí),而是倉儲(chǔ)方法的職責(zé)范圍,所以在這個(gè)場景下,我們只關(guān)心倉儲(chǔ)是否交付成功即可。

簡單舉例,測試用例代碼如下:

圖片

第二步,根據(jù)這些已知用例實(shí)現(xiàn)"房間狀態(tài)流轉(zhuǎn)為開播",也就是實(shí)現(xiàn) IRoomStatus 這個(gè)對(duì)象的 ChangeRoomStatusToStartLive 方法。

圖片

第三步,運(yùn)行第一步編寫的測試用例,查看是否符合預(yù)期。對(duì)于領(lǐng)域?qū)ο缶唧w依賴的Repository,由于我們事先在六邊形架構(gòu)中聲明了依賴的interface契約,所以可以較為簡單地使用mock處理這些依賴。

圖片

第四步,運(yùn)行完整的測試用例集合,如果不符合預(yù)期,則重回第二步,開始新一輪的修改和測試流程。

至此,一套完整的 UTDD 流程就良好地運(yùn)作起來了,在實(shí)際的開發(fā)過程中,我們的每一個(gè)領(lǐng)域?qū)ο?、倉儲(chǔ)方法、基礎(chǔ)設(shè)施的實(shí)現(xiàn)流程都是按照該流程進(jìn)行的,在很大程度上保障了新開播的內(nèi)建質(zhì)量。

對(duì)于更為大型的場景,比如application層對(duì)開播接口的測試,本質(zhì)上在六邊形架構(gòu)中也可以將集成的多個(gè)領(lǐng)域?qū)ο笸ㄟ^端口-適配器的解耦,將涉及的領(lǐng)域?qū)ο笾苯舆M(jìn)行mock,從而以較低的心智成本編寫出可讀性較高的集成測試,一個(gè)典型的集成測試集合如下:

圖片

在TDD思想的指導(dǎo)和開發(fā)流程下,我們的新服務(wù)整體單元測試覆蓋率達(dá)到了70+%,部分關(guān)鍵領(lǐng)域邏輯的覆蓋率達(dá)到100%。

如此的覆蓋率,不論在業(yè)務(wù)理解層面還是內(nèi)建質(zhì)量方面都產(chǎn)生了莫大的幫助——不必?fù)?dān)心一些改動(dòng)導(dǎo)致的重要影響無法被開發(fā)者捕捉到,這無疑在未來的業(yè)務(wù)迭代和進(jìn)一步重構(gòu)中都會(huì)起到關(guān)鍵作用。

4 安全的系統(tǒng)遷移

兵馬未動(dòng),糧草先行。

——《孫子兵法》

一艘巨輪建造完成后終究需要下水,而往往船下水的方案設(shè)計(jì)是先于船體本身的建造的。開播能被稱為遺留系統(tǒng),那么它背后的歷史邏輯和技術(shù)債務(wù)一定不容小覷,我們對(duì)新開播系統(tǒng)"完工下水"這件事,顯然就要謹(jǐn)慎對(duì)待了,從新開播的實(shí)現(xiàn)本身、到中間的開發(fā)執(zhí)行和驗(yàn)證,以及最后的部署灰度,都需要進(jìn)行細(xì)致的考慮,保證這艘新船能順利接觸水面。

前期對(duì)業(yè)務(wù)邏輯進(jìn)行最細(xì)致的歸納,這其中包括了代碼的逐行校對(duì)和每個(gè)邏輯分支的業(yè)務(wù)邏輯梳理,甚至也包括了PHP和Golang基礎(chǔ)組件的源碼對(duì)比。

中期在代碼編寫的過程中逐步明確"檢查點(diǎn)"和事件溯源的全貌,設(shè)計(jì)并完善了驗(yàn)證方案:流量復(fù)制和事件溯源,并構(gòu)建完善的新舊開播檢查點(diǎn)對(duì)比系統(tǒng),保證關(guān)鍵的邏輯節(jié)點(diǎn)上,新舊服務(wù)的表現(xiàn)完全一致。

后期在服務(wù)部署和灰度策略上,也做了最周密的準(zhǔn)備,包括網(wǎng)關(guān)級(jí)別萬分位的灰度放量規(guī)則和業(yè)務(wù)級(jí)別的重要房間退避方案。

 4.1 業(yè)務(wù)邏輯

業(yè)務(wù)邏輯通常是最沒有邏輯的東西。

—— Martin Fowler,《企業(yè)應(yīng)用架構(gòu)模式》

4.1.1 歷史邏輯

面對(duì)已存在多年的業(yè)務(wù)邏輯,不論它是否容易閱讀、我們是否熟悉跨語言的寫法,都應(yīng)該心存敬畏,逐個(gè)分支、逐個(gè)業(yè)務(wù)場景進(jìn)行盤點(diǎn),最終形成對(duì)此業(yè)務(wù)場景的正確理解。

面對(duì)這種高準(zhǔn)確度要求的表達(dá)訴求,我們選擇了已有的接口自動(dòng)化測試用例結(jié)合手動(dòng)端到端驗(yàn)證 + 逐行閱讀對(duì)比代碼的方式進(jìn)行梳理驗(yàn)證,最后以時(shí)序圖的方式,將舊開播服務(wù)的PHP實(shí)現(xiàn)邏輯呈現(xiàn)到施工方案中。

圖片

(涉及具體業(yè)務(wù)流程,僅展示縮略圖)

既然是"重構(gòu)",我們選擇盡量保持原有的邏輯流和數(shù)據(jù)流,先將主邏輯大部分遷移完成,再進(jìn)行下一步的改造。

所以在新版本的重構(gòu)中,涉及的業(yè)務(wù)邏輯流,實(shí)際上并沒有過大的改變,從而保障了邏輯分支在端到端表現(xiàn)上可以完全一致。

4.1.2 轉(zhuǎn)化漏斗圖

針對(duì)上述邏輯分支眾多的用例場景,我們也嘗試使用最直觀的圖形形式,展現(xiàn)給對(duì)開播領(lǐng)域不甚熟悉的研發(fā)同學(xué),甚至是產(chǎn)運(yùn)同學(xué)進(jìn)行參考,最終選擇了漏斗圖的形式。

最頂層為開播接口的入口,對(duì)應(yīng)直播姬點(diǎn)擊"開始直播"按鈕后對(duì)服務(wù)端開播接口的請(qǐng)求。而后的一系列漏斗層,則代表了服務(wù)端的行為,中途不斷有檢查項(xiàng)攔截不符合開播條件的請(qǐng)求,直到底部的成功開播。

圖片

4.2 流量復(fù)制 & 事件溯源

以上文歸納的"業(yè)務(wù)邏輯"為指導(dǎo),我們著手構(gòu)建了一套為開播業(yè)務(wù)邏輯遷移量身打造的流量復(fù)制和數(shù)據(jù)驗(yàn)證方案。

作為核心場景,開播日均承載的流量大,且邏輯流具有不確定性:不同的開播賬號(hào)、開播場景,甚至是網(wǎng)絡(luò)環(huán)境,都可能會(huì)導(dǎo)致會(huì)走入某一個(gè)上述復(fù)雜的開播邏輯分支中,可能有業(yè)務(wù)邏輯拒絕開播直接中斷開播流程的情況,也有可能發(fā)生內(nèi)部錯(cuò)誤但繼續(xù)執(zhí)行開播流程的情況。所以如何在眾多的業(yè)務(wù)分支中識(shí)別出新舊開播服務(wù)的數(shù)據(jù)流和邏輯流完全一致,是本次工程中的難點(diǎn)之一。

對(duì)此,我們?cè)O(shè)計(jì)了一套"流量復(fù)制"和"事件溯源"的驗(yàn)證方案。

4.2.1 流量復(fù)制

圖片

在舊版和新版開播正式進(jìn)行切換之前,必須保證新版舊版開播邏輯和數(shù)據(jù)鏈路和業(yè)務(wù)邏輯一致,為此我們?cè)O(shè)計(jì)了"流量復(fù)制"和"事件溯源/對(duì)賬"的機(jī)制。

  • 流量復(fù)制:網(wǎng)關(guān)層復(fù)制開播接口請(qǐng)求,分發(fā)給舊服務(wù)和新服務(wù)
  • 事件溯源/對(duì)賬:
  • 開播接口邏輯中的重要事件節(jié)點(diǎn)(包括了開播接口最終返回到端上的響應(yīng)值)上報(bào)到數(shù)據(jù)平臺(tái)
  • 對(duì)每一條開播請(qǐng)求的事件進(jìn)行新舊服務(wù)對(duì)比

對(duì)于復(fù)制過來的一組流量,我們期望它是冪等的,不能對(duì)下游數(shù)據(jù)產(chǎn)生任何影響。

在上文開發(fā)架構(gòu)中提到,Repository和Infrastructure在六邊形架構(gòu)中,可以通過不同的方式實(shí)現(xiàn)契約,那么對(duì)于"不真實(shí)執(zhí)行"這一實(shí)現(xiàn)方式,是天然可以實(shí)現(xiàn)支持的——新增一組"假寫"的適配器即可。

為保證這些檢查點(diǎn)在新舊服務(wù)完全一致,在驗(yàn)證方案中設(shè)計(jì)了以下三個(gè)階段:

圖片

圖片

圖片

  1. 舊服務(wù)進(jìn)行開播事件上報(bào)
  2. 主要邏輯仍然由舊服務(wù)處理,網(wǎng)關(guān)服務(wù)復(fù)制流量到新服務(wù)。新服務(wù)只執(zhí)行冪等邏輯,不進(jìn)行真實(shí)的寫操作。新舊服務(wù)均上報(bào)關(guān)鍵事件檢查點(diǎn),統(tǒng)一在數(shù)據(jù)平臺(tái)進(jìn)行每一條請(qǐng)求的檢查。
  3. 重構(gòu)部分的關(guān)鍵事件檢查點(diǎn)驗(yàn)證完成后,舊服務(wù)不再上報(bào),而新服務(wù)切換為真實(shí)寫入的模式,并且繼續(xù)保留關(guān)鍵事件上報(bào)能力。

4.2.2 事件溯源

借助戰(zhàn)略設(shè)計(jì)章節(jié)中的"事件風(fēng)暴"整理出的關(guān)鍵路徑和事件,稍加整理就可以得到一組關(guān)鍵事件鏈路,借助事件溯源(Event Sourcing)的思想,我們可以將開播流程中的重要節(jié)點(diǎn)上報(bào)并持久化。

根據(jù)事件風(fēng)暴和業(yè)務(wù)邏輯的時(shí)序圖,我們?cè)O(shè)定了以下關(guān)鍵事件檢查點(diǎn):

圖片

根據(jù)這些檢查點(diǎn),我們?cè)谛屡f版本的開播代碼中進(jìn)行改造,在對(duì)應(yīng)的點(diǎn)位埋樁進(jìn)行數(shù)據(jù)上報(bào)。由于他們可以被聚合在同一條trace下,所以針對(duì)每一條開播接口的請(qǐng)求,都可以被完整地記錄在案。

從事件溯源中,我們也可以獲取到一個(gè)意料之中的收獲:開播鏈路在服務(wù)端鏈路的業(yè)務(wù)可觀測性。

圖片

4.3 自動(dòng)化測試

4.3.1 UTDD 單元測試&集成測試

如 3.3(測試驅(qū)動(dòng)開發(fā))部分所述,新開播服務(wù)在開發(fā)時(shí)就采用了 TDD 工作流,單測覆蓋率70%以上,關(guān)鍵邏輯的行覆蓋率達(dá)到100%。

單元測試覆蓋率檢查集成到CI中,保證后續(xù)業(yè)務(wù)迭代質(zhì)量。

圖片

4.3.2 ATDD 測試共建自動(dòng)化測試用例

在本次重構(gòu)中,我們與測試團(tuán)隊(duì)持續(xù)合作,共建了200+條開播接口的自動(dòng)化集成測試用例,覆蓋了大部分的請(qǐng)求參數(shù)檢查、用戶身份和狀態(tài)、特殊開播場景、安全管控策略、分區(qū)和場次狀態(tài)等正常和異常用例,并對(duì)對(duì)應(yīng)預(yù)期接口返回結(jié)果、數(shù)據(jù)和消息寫入結(jié)果等檢查。同時(shí)在自動(dòng)化測試中引入diff能力,相同參數(shù)輸入下新舊服務(wù)接口響應(yīng)進(jìn)行對(duì)比,覆蓋80%以上開播場景。

圖片

整個(gè)重構(gòu)的遷移過程中,我們通過接口自動(dòng)化測試,發(fā)現(xiàn)并修復(fù)問題10+個(gè)。

4.4 部署計(jì)劃

整體上線(包括流量復(fù)制&實(shí)際灰度階段)分為了三個(gè)階段:

  • 舊開播服務(wù)事件上報(bào)
  • 新舊開播服務(wù),線上流量復(fù)制對(duì)比
  • 新開播服務(wù)正式灰度切流

整個(gè)部署發(fā)布不同階段,都嚴(yán)格制定SOP按照計(jì)劃執(zhí)行,避免遺漏或切換過程中對(duì)線上開播服務(wù)的影響:

圖片

4.5 結(jié)果

在精細(xì)的驗(yàn)證計(jì)劃、部署計(jì)劃和嚴(yán)格的流程把控下,開播在整個(gè)遷移過程中未出現(xiàn)任何事故。

其中一些驗(yàn)證操作的功效是很直觀的:

  • ATDD 接口自動(dòng)化發(fā)現(xiàn)差異:10+個(gè)
  • 流量復(fù)制&事件溯源發(fā)現(xiàn)差異:20+個(gè)
  • 歷史邏輯&代碼對(duì)比發(fā)現(xiàn)差異:30+個(gè)

同時(shí)我們?cè)谝粋€(gè)月時(shí)間內(nèi),逐步進(jìn)行了精細(xì)到單個(gè)用戶粒度-萬分位-千分位-十分位-全量的灰度,在途中也優(yōu)化了10+性能問題。

圖片

最終順利全量上線。

5 生產(chǎn)配套

“君之所以明者,兼聽也;其所以暗者,偏信也?!薄獫h·王符《潛夫論·明暗》

一個(gè)運(yùn)作良好的系統(tǒng)首先必須具備良好的可觀測性,倘若都無法觀測到各個(gè)零件運(yùn)作是否良好,又談何算得上一輛好車。

對(duì)于開播這種不容有失的系統(tǒng),萬萬不可寫完代碼就萬事大吉。我們需要更加謹(jǐn)慎地將系統(tǒng)的運(yùn)作狀態(tài)觀測納入設(shè)計(jì)考慮,讓觀測變得更加直觀,使?jié)撛诘南到y(tǒng)性風(fēng)險(xiǎn)可以快速暴露,也便于在緊急情況下做出恰當(dāng)?shù)臎Q策。

5.1 系統(tǒng)監(jiān)控

對(duì)于開播服務(wù)的整體鏈路,我們通過前文的事件溯源上報(bào)方案結(jié)合司內(nèi)的監(jiān)控解決方案,對(duì)開播成功、開播拒絕的情況進(jìn)行了上報(bào)統(tǒng)計(jì),對(duì)開播整體大盤的開播成功率、被拒絕開播的原因和發(fā)生率形成直觀感受。

若開播系統(tǒng)出現(xiàn)了某種業(yè)務(wù)異動(dòng),比如被拒絕開播的突增,我們可以借助監(jiān)控大盤和告警體系在第一時(shí)間感知到。

 5.2 系統(tǒng)排障

伴隨著"事件溯源"體系的建設(shè),自然可以衍生出眾多提升系統(tǒng)可觀測性的輔助工具。這些工具在未來的業(yè)務(wù)運(yùn)維和業(yè)務(wù)迭代過程中可以節(jié)省大量的人力。

如可以實(shí)時(shí)驗(yàn)證是否指定房間是否滿足開播條件的"模擬開播":

圖片

以及針對(duì)每一條歷史開播請(qǐng)求可以追溯關(guān)鍵事件,排查開播為何成功/失敗的"開播事件問診":

圖片

6 結(jié)果

回顧文章開篇時(shí)提到的歷史債務(wù)上來,我們從業(yè)務(wù)層面和技術(shù)層面來進(jìn)行一些簡單的復(fù)盤。

6.1 業(yè)務(wù)收益

知識(shí)共享:在開播平臺(tái)重構(gòu)的一系列工作中,首當(dāng)其沖的是對(duì)開播歷史邏輯的完整梳理,這無疑提高了產(chǎn)研對(duì)開播業(yè)務(wù)的理解程度,降低溝通成本;在過程中,我們也已與產(chǎn)品溝通了眾多不曾關(guān)注到的功能細(xì)節(jié),幫助產(chǎn)品更好地建設(shè)開播工具生態(tài)。伴隨著產(chǎn)研對(duì)業(yè)務(wù)知識(shí)的理解成本降低,一些客訴問題的排查也會(huì)變得容易起來——從前一些只有代碼編寫者才能描述的邊緣情況,現(xiàn)在更容易被產(chǎn)品甚至熟悉的運(yùn)營所得知,進(jìn)而減低對(duì)開播功能的疑惑,最終使產(chǎn)研協(xié)作效率提升。

開發(fā)提效:在PHP舊服務(wù)的開發(fā)過程中,用例梳理、PHP代碼晦澀的Coding過程、復(fù)雜代碼的反復(fù)Review、PHP的遠(yuǎn)古工具鏈?zhǔn)褂枚紩?huì)占用大量的開發(fā)時(shí)間;相較舊版,新版開播接口不存在這些歷史包袱,極大提高了開發(fā)效率。

業(yè)務(wù)SRE:"開播事件溯源"提供的接口請(qǐng)求級(jí)別的問診能力,不同于以往排查開播問題時(shí)需要手動(dòng)翻閱每一條關(guān)鍵日志,新版本的一鍵查詢溯源記錄能力可以大大降低研發(fā)的問題排查成本。

6.2 系統(tǒng)性風(fēng)險(xiǎn)優(yōu)化

在過去,開播系統(tǒng)運(yùn)行于"房間服務(wù)"的 PHP 服務(wù)之中,該服務(wù)除了承載開播業(yè)務(wù),也承載了大量和直播有關(guān)的周邊業(yè)務(wù)接口;

從技術(shù)角度,跨語言的遷移解決了較多的風(fēng)險(xiǎn):

  • 一個(gè)相當(dāng)?shù)湫偷陌咐涸鹊目蚣芑?Swoole 二次開發(fā)(Worker 模式),在突發(fā)并發(fā)流量較大時(shí)會(huì)出現(xiàn)單實(shí)例 Worker 滿載的情況,造成請(qǐng)求超時(shí);且由于請(qǐng)求堆積、Worker 釋放和重建、內(nèi)存回收之間存在一定時(shí)間差,瞬時(shí) Swoole Worker 進(jìn)程超出內(nèi)存限制導(dǎo)致請(qǐng)求失敗時(shí)有發(fā)生。該問題造成了原開播系統(tǒng)的穩(wěn)定性不足,無法支撐直播業(yè)務(wù)快速發(fā)展的需要,構(gòu)成了系統(tǒng)性風(fēng)險(xiǎn)。而這種在PHP框架下難以根治的問題,遷移到Golang后就自然不存在了。
  • 重構(gòu)后,開播業(yè)務(wù)屬于單獨(dú)的 Go 微服務(wù),性能和可用性上有了大幅提升,接口可用性 SLA 從 99.xx% 提升到 99.99xx%,接口 P99 響應(yīng)速度提高50+%。
  • 從語言層面,由于 PHP 本身是弱類型和解釋型語言,在開發(fā)和編譯過程中較難發(fā)現(xiàn)潛在問題,導(dǎo)致研發(fā)自測和測試成本上升,在過去也曾因?yàn)檫@些特性的處理不慎導(dǎo)致開播系統(tǒng)的線上問題;Go服務(wù)中對(duì)單元測試、故障測試有較好的支持,可以及時(shí)發(fā)現(xiàn)問題。
  • PHP 的內(nèi)部工具鏈相對(duì)缺少維護(hù),與之對(duì)比的 Golang 是公司后端研發(fā)最主流的選型,公司級(jí)監(jiān)控、告警、觀測、服務(wù)治理、持續(xù)集成等系統(tǒng)都為 Go 提供了相對(duì)更好的支持,這也使得新的開播系統(tǒng)也有更好的可維護(hù)性和事件響應(yīng)能力。

圖片

從業(yè)務(wù)角度看,也提高了業(yè)務(wù)的系統(tǒng)穩(wěn)定性:

  • 重構(gòu)過程中,梳理了整個(gè)開播鏈路中的服務(wù)、接口、配置、存儲(chǔ)依賴,并將數(shù)十個(gè)依賴項(xiàng)區(qū)分為強(qiáng)依賴和弱依賴。
  • 對(duì)于強(qiáng)依賴場景,梳理了對(duì)應(yīng)的失敗表現(xiàn),并編寫了應(yīng)急SOP;對(duì)于弱依賴調(diào)用失敗的場景,采用補(bǔ)償任務(wù)等手段處理,不阻塞用戶開播,進(jìn)一步提升了開播系統(tǒng)的可用性。

6.3 技術(shù)資產(chǎn)

一個(gè)好的技術(shù)項(xiàng)目,不僅需要達(dá)成業(yè)務(wù)和技術(shù)上的硬性目標(biāo),還需要有所積累和成長。我們?cè)陂_播重構(gòu)的旅途中,也摸索出了一套行之有效、可復(fù)用的觀測模式和遷移模式。

6.3.1 更細(xì)粒度的業(yè)務(wù)可觀測

上文中提到的業(yè)務(wù)鏈路可觀測,沉淀后也成為了開播問診臺(tái)的通用事件溯源功能。

可查看某個(gè)房間過往不同開播場次,過程 & 結(jié)果關(guān)鍵事件的數(shù)據(jù)信息,更快的定位到線上每一次具體開播的情況。

6.3.2 可復(fù)用的遷移模式

經(jīng)過本次開播接口遷移的歷練,開播平臺(tái)獲得了可復(fù)用的PHP轉(zhuǎn)Go的工程經(jīng)驗(yàn),我們也可以嘗試用DDD的觀點(diǎn)來總結(jié):

圖片

一些沉淀的能力如下:

  • 業(yè)務(wù)流
  • 業(yè)務(wù)邏輯梳理:邏輯分支級(jí)別梳理,落實(shí)到標(biāo)準(zhǔn)設(shè)計(jì)圖上(時(shí)序圖、數(shù)據(jù)流圖),分支級(jí)別的測試用例覆蓋(判定表、單測、自動(dòng)化測試)
  • 事件溯源:按照事件風(fēng)暴、測試用例標(biāo)定的關(guān)鍵事件,進(jìn)行開播流程中的事件上報(bào),保證請(qǐng)求可完全回溯。
  • 工程保障
  • 流量復(fù)制/切換:新舊接口的流量復(fù)制和流量控制。
  • SOP:嚴(yán)格的部署計(jì)劃以及SOP,減少人為因素的不穩(wěn)定性。

那么套用回“開播平臺(tái)遷移”這個(gè)問題域匯總,我們可以得到以下的解法:

  • 統(tǒng)一領(lǐng)域知識(shí):對(duì)于產(chǎn)研測需要達(dá)成的共識(shí),一套可讀性高的業(yè)務(wù)邏輯梳理可以滿足訴求。
  • 白盒驗(yàn)證:業(yè)務(wù)邏輯梳理提供單元測試、自動(dòng)化測試用例;事件溯源和流量復(fù)制共同提供新舊服務(wù)的關(guān)鍵事件上報(bào)、數(shù)據(jù)對(duì)比。
  • 服務(wù)部署:SOP提供嚴(yán)格的準(zhǔn)出和操作步驟;流量復(fù)制/切換提供新舊服務(wù)的切換能力。
  • 戰(zhàn)術(shù)落地:借助于上述的所有能力,逐步完善開播系統(tǒng)。

一個(gè)完整的迭代可能是這樣的:確保項(xiàng)目組內(nèi)產(chǎn)研認(rèn)知一致后,按照TDD方法編寫出初版代碼;通過眾多測試用例后,進(jìn)行流量復(fù)制和事件溯源,通過關(guān)鍵事件對(duì)比保障關(guān)鍵檢查點(diǎn)和數(shù)據(jù)鏈路完全一致,最終按照SOP進(jìn)行上線。如果中途發(fā)現(xiàn)了修改點(diǎn),需要回退到初版代碼編寫,乃至同一領(lǐng)域知識(shí)的步驟進(jìn)行項(xiàng)目組認(rèn)知的對(duì)齊。

通過業(yè)務(wù)流的完整評(píng)估,再有嚴(yán)謹(jǐn)?shù)墓こ舔?yàn)證計(jì)劃保障,在事實(shí)上極大降低了出現(xiàn)嚴(yán)重遷移事故的概率(開播遷移過程中未出現(xiàn)PX以上事故)。

7 后日談:可演進(jìn)的“遺留”系統(tǒng)

重構(gòu)和微服務(wù)的締造者,軟件開發(fā)領(lǐng)域的泰斗,Martin Fowler 曾經(jīng)說過這樣一句話:

Let's face it, all we are doing is writing tomorrow's legacy software today.

是的,可以毫不夸張地說,你現(xiàn)在所寫的每一行代碼,都是未來的遺留系統(tǒng)。這聽上去有點(diǎn)讓人沮喪,但卻是血淋淋的事實(shí),一個(gè)軟件系統(tǒng)的生命周期終歸會(huì)符合業(yè)務(wù)演進(jìn)的客觀規(guī)律。

不過大可不必氣餒,回到我們?cè)谝灾姓劦降倪z留系統(tǒng)定義,有些系統(tǒng)時(shí)間雖長,但如果一直堅(jiān)持現(xiàn)代化的開發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測試策略、DevOps 等方面都保持先進(jìn)性,就算將來需要進(jìn)行架構(gòu)的進(jìn)一步演進(jìn),這樣"整潔"的老系統(tǒng)也會(huì)幫助我們規(guī)避眾多的問題,甚至可以讓演進(jìn)周期縮短、演進(jìn)風(fēng)險(xiǎn)降低。

相信我們今天在開播平臺(tái)遷移中花費(fèi)的心血和留下的基石,終會(huì)為"歷久彌新"的系統(tǒng)打下基礎(chǔ)。

參考:

[1] Vernon, V. (2013) Implementing domain-driven design.

[2] Martraire, C. (2019) Living documentation: Continuous knowledge sharing by design. Boston: Addison-Wesley.

[3] Just enough software architecture: A risk-driven approach. Boulder: Marshall & Brainerd, 2010.

[4] Fowler, M. (2019) Refactoring: Improving the design of existing code. Boston: Addison-Wesley.

[5] Qilin, Y. (2021) 遺留系統(tǒng)
當(dāng)前文章:B站大型開播平臺(tái)重構(gòu)
URL分享:http://www.5511xx.com/article/dhjisih.html