發(fā)的核心:從業(yè)務(wù)需求到系統(tǒng)設(shè)計(jì)的思考路徑)
剛轉(zhuǎn)后端那年我電腦里存著幾十個(gè)框架的 Demo每個(gè)都能跑起來(lái)。直到一次需求評(píng)審業(yè)務(wù)方說(shuō)要做一個(gè)“數(shù)據(jù)大屏”我們對(duì)著一堆指標(biāo)爭(zhēng)論了兩周才終于搞清楚他要的是老板來(lái)視察時(shí)能看到的動(dòng)態(tài)數(shù)字。那一刻我意識(shí)到后端開(kāi)發(fā)的核心從來(lái)不是框架和中間件而是一條從業(yè)務(wù)需求到系統(tǒng)設(shè)計(jì)的思考路徑。這條路徑踩得深不深直接決定系統(tǒng)是解決問(wèn)題的工具還是制造問(wèn)題的源頭。需求的真相藏在對(duì)話(huà)的縫隙里業(yè)務(wù)方描述需求時(shí)用的詞帶著自己的語(yǔ)境。他說(shuō)“加一個(gè)導(dǎo)出功能”你以為是 Excel 導(dǎo)出但他真正想要的是每天自動(dòng)把報(bào)表發(fā)到郵箱。這些沒(méi)說(shuō)出來(lái)的部分才是需求的核心。業(yè)務(wù)方說(shuō)的永遠(yuǎn)不是需求而是他腦中對(duì)解決方案的一次快照。你要做的是把快照拆開(kāi)逐個(gè)角落問(wèn)清楚誰(shuí)看這個(gè)導(dǎo)出數(shù)據(jù)導(dǎo)出多少量多久一次網(wǎng)絡(luò)斷了怎么辦權(quán)限如何控制這些問(wèn)題看似瑣碎但每一個(gè)都能改變?cè)O(shè)計(jì)。后端工程師最怕聽(tīng)到“需求很清楚”這種話(huà)因?yàn)榍宄清e(cuò)覺(jué)。提需求的人想要的是結(jié)果而你最該問(wèn)的是結(jié)果長(zhǎng)什么樣。需求評(píng)審不是走過(guò)場(chǎng)而是用提問(wèn)剝洋蔥直到露出真實(shí)的業(yè)務(wù)目標(biāo)。這里有一個(gè)殘酷的事實(shí)大多數(shù)需求在第一次描述時(shí)都是錯(cuò)的包括業(yè)務(wù)方自己的理解。抽象是取舍的藝術(shù)需求理解透了下一步是把業(yè)務(wù)還原成系統(tǒng)模型。這里的關(guān)鍵是抽象。抽象不是把現(xiàn)實(shí)世界照搬進(jìn)數(shù)據(jù)庫(kù)而是刻意忽略一些細(xì)節(jié)保留關(guān)鍵關(guān)系和不變屬性。比如用戶(hù)這個(gè)概念在登錄場(chǎng)景是賬號(hào)和密碼在訂單場(chǎng)景是收貨地址和支付方式在風(fēng)控場(chǎng)景是行為軌跡。同一個(gè)用戶(hù)不同上下文里抽象完全不同。沒(méi)有完美的抽象只有當(dāng)前成本最小的抽象。很多系統(tǒng)做砸是因?yàn)橐婚_(kāi)始想“全都要”把實(shí)體設(shè)計(jì)得無(wú)比龐大結(jié)果每個(gè)功能都綁手綁腳。好的抽象應(yīng)該讓新需求像填空壞的抽象讓新需求像拆墻。好的抽象讓新需求像填空壞的抽象讓新需求像拆墻。你可以常問(wèn)自己如果明天要加一個(gè)用戶(hù)類(lèi)型我的模型需要改幾張表如果答案超過(guò)兩張抽象可能有問(wèn)題。抽象還是一門(mén)遺忘的藝術(shù)忘掉那些和當(dāng)前業(yè)務(wù)無(wú)關(guān)的枝節(jié)才能讓系統(tǒng)呼吸。數(shù)據(jù)是系統(tǒng)的骨架流程是血肉系統(tǒng)設(shè)計(jì)最實(shí)在的落腳點(diǎn)是數(shù)據(jù)模型和狀態(tài)流程。拿到需求先畫(huà)出核心實(shí)體之間的關(guān)系再為每個(gè)實(shí)體畫(huà)出生命周期。訂單狀態(tài)的遷移——待支付、已支付、已取消、退款中——每一個(gè)邊都是一條業(yè)務(wù)規(guī)則。狀態(tài)機(jī)不畫(huà)清楚線(xiàn)上必出鬼。我記得一個(gè)支付系統(tǒng)因?yàn)闆](méi)考慮超時(shí)關(guān)閉導(dǎo)致用戶(hù)取消訂單后仍然可以支付資金對(duì)不上賬。如果當(dāng)初把狀態(tài)機(jī)畫(huà)在一張紙上這些坑都能避免。數(shù)據(jù)字段同樣要問(wèn)來(lái)源這個(gè)字段是用戶(hù)填的還是系統(tǒng)生成的它的有效性由誰(shuí)來(lái)保障會(huì)不會(huì)有多處寫(xiě)入不會(huì)回答數(shù)據(jù)來(lái)源和流向的系統(tǒng)設(shè)計(jì)都是空中樓閣。數(shù)據(jù)庫(kù)表不是草稿紙每一列都應(yīng)有明確的主人。數(shù)據(jù)一致性在后端尤其棘手分布式系統(tǒng)里你不得不引入冪等、重試、對(duì)賬但這些都應(yīng)該由業(yè)務(wù)需求觸發(fā)而不是為了技術(shù)炫技。接口不是 URL而是契約數(shù)據(jù)模型定了就要定義系統(tǒng)對(duì)外的接口。接口不是方法的羅列而是與調(diào)用方之間的契約。接口的命名比代碼注釋重要一百倍。命名一旦定義所有調(diào)用方都基于它溝通糟糕的命名讓人不敢調(diào)用良好的命名讓團(tuán)隊(duì)不用看文檔也能猜對(duì)。設(shè)計(jì)接口時(shí)先約定請(qǐng)求與響應(yīng)的結(jié)構(gòu)再談實(shí)現(xiàn)。最好把錯(cuò)誤碼也一同設(shè)計(jì)讓調(diào)用方知道怎么處理而不是只看到一個(gè) 500。還要考慮冪等尤其支付、通知、消息類(lèi)接口。后端設(shè)計(jì)得差不是代碼亂是接口讓人不敢動(dòng)。不要將數(shù)據(jù)庫(kù)表直接暴露出去要構(gòu)建面向場(chǎng)景的響應(yīng)模型避免內(nèi)部改動(dòng)波及外部。接口版本要規(guī)劃兼容策略但更重要的是一開(kāi)始就謹(jǐn)慎避免不必要的破壞性變更。契約的意義在于它是團(tuán)隊(duì)協(xié)作的錨點(diǎn)錨點(diǎn)穩(wěn)了船才不會(huì)飄走。架構(gòu)是演化出來(lái)的不是設(shè)計(jì)出來(lái)的沒(méi)有一套架構(gòu)能一步到位業(yè)務(wù)在變團(tuán)隊(duì)在變流量也在變。后端架構(gòu)更像一棵樹(shù)不斷長(zhǎng)出新的枝干而不是一棟一次性澆筑的大樓。但成長(zhǎng)需要方向所以設(shè)計(jì)時(shí)要有意識(shí)地區(qū)分可逆決策和不可逆決策??赡娴谋热缛罩靖袷健⒑瘮?shù)命名隨意一點(diǎn)不可逆的比如數(shù)據(jù)庫(kù)分庫(kù)分表、跨服務(wù)數(shù)據(jù)共享必須慎重。把不可逆的決策留到最后是架構(gòu)演化的第一原則。許多團(tuán)隊(duì)一上來(lái)就拆微服務(wù)、引入高可用集群結(jié)果業(yè)務(wù)還沒(méi)驗(yàn)證運(yùn)維已經(jīng)累垮。系統(tǒng)不會(huì)死于欠設(shè)計(jì)只會(huì)死于過(guò)度設(shè)計(jì)。我這里說(shuō)的欠設(shè)計(jì)是面對(duì)真實(shí)需求的無(wú)力而不是對(duì)未來(lái)臆想的無(wú)視。給系統(tǒng)留出演化空間比如模塊間用接口隔離比預(yù)先鋪一堆組件更實(shí)際。架構(gòu)師該做的不是預(yù)測(cè)未來(lái)而是讓未來(lái)發(fā)生時(shí)改動(dòng)仍然可控。邊界即權(quán)力當(dāng)系統(tǒng)需要多個(gè)模塊或多團(tuán)隊(duì)協(xié)作時(shí)邊界設(shè)計(jì)就成了核心。邊界劃分本質(zhì)上是權(quán)力分配數(shù)據(jù)由誰(shuí)寫(xiě)接口由誰(shuí)定流程由誰(shuí)驅(qū)動(dòng)。劃分不清的邊界最終都會(huì)變成撕扯不清的鍋。一個(gè)經(jīng)典場(chǎng)景是“下單”和“庫(kù)存”。訂單服務(wù)扣庫(kù)存庫(kù)存服務(wù)也扣庫(kù)存看似都行一旦超賣(mài)就是事故兩邊互相推諉。按業(yè)務(wù)領(lǐng)域劃分邊界每個(gè)領(lǐng)域的內(nèi)部規(guī)則和存儲(chǔ)完全自治對(duì)外只暴露經(jīng)過(guò)設(shè)計(jì)的事件。訂單完成時(shí)發(fā)布事件庫(kù)存服務(wù)監(jiān)聽(tīng)事件來(lái)扣減誰(shuí)也不會(huì)越界。依賴(lài)倒置不是算法是團(tuán)隊(duì)溝通的規(guī)則。邊界寫(xiě)進(jìn)代碼里成為無(wú)法繞過(guò)的檢查比依賴(lài)人的自覺(jué)可靠得多。設(shè)計(jì)邊界時(shí)也要尊重業(yè)務(wù)的語(yǔ)言體系不要在庫(kù)存領(lǐng)域談?wù)撚唵蔚摹跋聠谓痤~”那會(huì)讓人糊涂。技術(shù)選型是一場(chǎng)負(fù)債管理架構(gòu)骨架有了技術(shù)棧的選擇就提上日程。很多人喜歡追新但選型不是選美而是在選擇未來(lái)要背的負(fù)債。沒(méi)有最好的技術(shù)只有最貴的遷移成本。引入一個(gè)中間件意味著團(tuán)隊(duì)要學(xué)習(xí)、運(yùn)維要監(jiān)控、故障要排查。所以選型之前先量化需求并發(fā)量級(jí)、數(shù)據(jù)規(guī)模、延遲要求、一致性要求。能用數(shù)據(jù)庫(kù)解決的事情不要引入緩存能用消息隊(duì)列解決的事情不要引入服務(wù)網(wǎng)格。能簡(jiǎn)單就不要復(fù)雜能少一個(gè)中間件就少一個(gè)故障點(diǎn)。同時(shí)要做技術(shù)決策記錄把選擇理由和權(quán)衡寫(xiě)下來(lái)讓后來(lái)者知道當(dāng)時(shí)為什么這么定而不是看到一段代碼一頭霧水。技術(shù)選型還必須考慮團(tuán)隊(duì)的現(xiàn)實(shí)一個(gè)沒(méi)人會(huì)的技術(shù)哪怕再好也沒(méi)人維護(hù)最后變成遺產(chǎn)。選型是面向整個(gè)系統(tǒng)生命周期的不是面向簡(jiǎn)歷的。需求變更的應(yīng)對(duì)設(shè)計(jì)得再好也攔不住需求變化。后端工程師的態(tài)度應(yīng)該是擁抱變化而不是抗拒。但擁抱不是被動(dòng)改代碼而是用設(shè)計(jì)讓變化成本可控。在需求中找出大概率會(huì)變動(dòng)的維度比如計(jì)費(fèi)規(guī)則、渠道類(lèi)型、優(yōu)惠策略為它們?cè)O(shè)置擴(kuò)展點(diǎn)。比如用策略模式封裝規(guī)則或者用事件驅(qū)動(dòng)解耦響應(yīng)方。為不存在的未來(lái)預(yù)留接口是最昂貴的浪費(fèi)。擴(kuò)展點(diǎn)只能加在真實(shí)業(yè)務(wù)的波動(dòng)處而不是憑空想象。當(dāng)需求變更到來(lái)時(shí)先問(wèn)影響范圍再?zèng)Q定改動(dòng)方案。如果能只改一個(gè)類(lèi)就絕不動(dòng)三個(gè)表。真正的設(shè)計(jì)彈性體現(xiàn)在改一行代碼能完成需求而不是新增一個(gè)框架。需求變更是一場(chǎng)壓力測(cè)試它檢驗(yàn)的不是代碼寫(xiě)得多快而是當(dāng)初的抽象是否貼近業(yè)務(wù)本質(zhì)。那些被變更打垮的系統(tǒng)通常不是代碼太爛而是設(shè)計(jì)的核心邏輯沒(méi)有扎在業(yè)務(wù)深處。那次數(shù)據(jù)大屏需求我們只用了一張定時(shí)刷新的圖表頁(yè)配合一個(gè)簡(jiǎn)單的聚合查詢(xún)。業(yè)務(wù)方很滿(mǎn)意因?yàn)樗闹皇恰白尷习蹇粗娣薄N覜](méi)有展示任何高并發(fā)技巧但那次經(jīng)歷讓我完成了從“會(huì)寫(xiě)代碼”到“會(huì)做設(shè)計(jì)”的轉(zhuǎn)變。后端開(kāi)發(fā)的本質(zhì)是一場(chǎng)與“不確定性”的持續(xù)談判。需求是不確定的技術(shù)選型是不確定的架構(gòu)演化也是不確定的。你所能做的就是不斷加深對(duì)業(yè)務(wù)的理解并用克制的設(shè)計(jì)去應(yīng)對(duì)。代碼只是思考的副產(chǎn)品。真正的后端核心是一條清晰、謙遜、充滿(mǎn)取舍的思考路徑這條路值得用整個(gè)職業(yè)生涯去走。