成為工程師 - 搭建系統(tǒng)先搭建框架
作為工程師,日常的工作基本上都是圍繞著【系統(tǒng)】展開的?!敬罱ㄒ粋€系統(tǒng)】是工程師必須具備的最基礎(chǔ)能力。
從業(yè)至今,我自己負責(zé)過很多系統(tǒng),也看到過很多系統(tǒng)。有的系統(tǒng)搭建得非常優(yōu)雅,無論是可讀性還是擴展性都非常好。說白了就是代碼看起來清晰干凈,研發(fā)起來快捷且安全,排查問題也容易定位。但還有一些系統(tǒng)你就是看上好幾遍代碼都捋不清邏輯,改造的時候更是無從下手。
一個系統(tǒng)存在復(fù)雜的業(yè)務(wù)邏輯是正常的,而一個優(yōu)雅的系統(tǒng)是能夠通過良好的結(jié)構(gòu)去管理這些復(fù)雜性。我把這個結(jié)構(gòu)稱之為【系統(tǒng)框架】。
搭建系統(tǒng)框架是系統(tǒng)建設(shè)的第一步,也是最重要一步。我們這篇文章就來聊聊如何搭建一個好的系統(tǒng)框架。
我們先看一個反例:
反例時間
假設(shè)我們有一個http接口,需要返回用戶的信息。用戶信息包括:用戶昵稱、用戶vip等級、用戶標(biāo)簽、用戶余額、余額歷史以來充值總額、用戶最貴一次消費。
下面的代碼是一種典型的實現(xiàn)方式(可以看注釋來了解步驟):
很多同學(xué)看到這樣的代碼覺得已經(jīng)挺好了。有日志、有注釋、有異常處理,代碼也沒有擠在一堆。但真的是這樣嗎?
我認為這樣的代碼確實反映了工程師一定的技術(shù)素養(yǎng),但起碼存在以下這些問題:
【Q1】如果新增接口,所有的日志打印要冗余寫一遍,包括入口日志、出口日志、異常日志。
【Q2】如果新增接口,try-catch的異常處理邏輯也需要冗余重寫。
【Q3】如果新增一個只獲取用戶金額信息的接口,需要冗余復(fù)制上述代碼中和金額相關(guān)的部分。
【Q4】如果接口需要修改,返回新的信息,那就需要往這個代碼里添加新的業(yè)務(wù)邏輯。而這個類一旦有變化,就涉及對這個類的回歸驗證。
【Q5】如果我要同時支持可以根據(jù)用戶昵稱來搜索用戶信息,那么我要新增一個基本完全一樣的接口(除了入?yún)⒉煌?/p>
(記住以上這些問題,我們下面會逐一來解決)
所以,如果用發(fā)展的眼光(需求新增)去看這段代碼,你可以基本判斷以后會存在大量的邏輯冗余。
大量的冗余會帶來研發(fā)的低效、升級的遺漏、邏輯不一致的風(fēng)險等等。
此外,不同工程師可能都有自己的編碼習(xí)慣,同樣是處理日志,異常,寫法可以迥然不同。
結(jié)合在大廠多年的經(jīng)驗,優(yōu)雅的系統(tǒng)會結(jié)合兩種設(shè)計方式來解決這類冗余的問題。我們一起來看看。
一、模板模式
【模板模式】就是設(shè)計模式中的“模板方法模式”。模板方法模式的核心思想就是:統(tǒng)一算法框架,暴露算法要素給子類來實現(xiàn)。
看定義還是抽象了一些,我們直接看例子。
下圖就是一個定義了算法框架的抽象模板類:
可以看到,process方法里僅做了兩件事:
【1】實現(xiàn)了所有接口的共用邏輯。比如打日志、計算耗時、捕獲異常并處理。
【2】確定了步驟(步驟也稱之為算法框架)。比如先校驗參數(shù),后執(zhí)行業(yè)務(wù)邏輯。
基于上面的模板,我們的服務(wù)只需要做如下實現(xiàn)就可以了:
下面我們根據(jù)【模板模式】的思想修改我們的反面案例,我們的代碼就變成了:
可以看到,通過模板模式,你起碼會有這樣幾個好處:
【1】每個接口都不用擔(dān)心忘了執(zhí)行必要的公共邏輯,例如打印日志、異常處理。
【2】不用擔(dān)心接口有遺漏步驟及搞錯步驟順序,例如入?yún)⑿r炘趫?zhí)行業(yè)務(wù)流程之前。
【3】接口只需要關(guān)心自己業(yè)務(wù)邏輯的實現(xiàn)即可。
【4】所有接口打印的日志及異常處理方式確保是一致的,方便監(jiān)控和定位問題。
【5】如果需要增加一些公用的能力,例如埋點上報某個統(tǒng)計平臺,只需要在框架中添加邏輯,所有接口都直接生效。
我們使用【模板模式】解決了業(yè)務(wù)無關(guān)邏輯的冗余問題,也就是上述針對反面例子提出的問題中的Q1、Q2,下一步我們要動手解決業(yè)務(wù)邏輯冗余的問題,也就是針對問題Q3、Q4、Q5。
二、流程引擎
流程引擎的核心思想是:將要執(zhí)行的邏輯看成是一個個步驟的串接,由統(tǒng)一的角色來管理步驟的執(zhí)行順序,這個角色就是流程引擎。
我們用兩張圖來對比下使用流程引擎和常規(guī)瀑布式編碼的不同。
1.流程式編碼 vs 瀑布式編碼
上圖分別展示了兩種編碼法方式【瀑布式編碼】和【流程式代碼】。
【瀑布式編碼】就是從上往下按照步驟把業(yè)務(wù)邏輯寫完。
【流程式編碼】是先把可以獨立的功能抽成一個個執(zhí)行器。不同的服務(wù)根據(jù)自己功能的需求來串接這些執(zhí)行器。
兩者對比,流程式編碼有這樣一些好處:
【避免冗余】:同樣的業(yè)務(wù)邏輯只有一份代碼。
【最小修改】:如果需要加一個環(huán)節(jié),只需要新增一個處理器,并且編排到流程中即可,對已有代碼沒有任何侵入。
【方便追蹤】:我們可以在每一個節(jié)點執(zhí)行完以后,在流程引擎中添加一些日志,以此來追蹤執(zhí)行過程。例如在哪里中斷了?哪個執(zhí)行器耗時最長?
【利于分工】:每個處理器約定好職責(zé)就可以獨立開發(fā),并且可以獨立測試。
【可讀性好】:流程式代碼往往在一處編輯所有的步驟,代碼可讀性佳??吹揭粋€流程由哪些節(jié)點組成,基本上就了解大概的邏輯了。
【靈活多變】:流程式編程還可以支持各個處理器以分支和循環(huán)的方式組合。
下面我們就來實現(xiàn)一個簡單的流程引擎,并用它來繼續(xù)改造上面的反例,以此來說明【流程式編程】的思想和好處。
2.處理器設(shè)計
我們先看下處理器的實現(xiàn),處理器是被我們抽取出來處理一塊業(yè)務(wù)邏輯的單元。如下圖標(biāo)識
在反例里,我們可以抽取三個處理器?!居脩粜畔⑻幚砥鳌俊窘痤~處理器】【消費記錄處理器】。處理器接口如下:
細心的同學(xué)可能會問,ProcessRequest和ProcessContext是什么?
【ProcessRequest】:對請求信息的封裝。例如用戶的userId、用戶的客戶端信息(IOS、安卓、以及對應(yīng)版本號)、要求轉(zhuǎn)賬的金額、轉(zhuǎn)賬對象等。每個處理器都能夠獲得這些信息,根據(jù)自己的需要去使用。ProcessRequest中所有的值,原則上不允許被修改,以免原始請求信息被污染。
【ProcessContext】:流程執(zhí)行的上下文,用于存放整個流程執(zhí)行過程中的數(shù)據(jù)。在所有執(zhí)行器處理完以后,結(jié)果組裝器可以從ProcessContext中獲取到各種結(jié)果數(shù)據(jù),構(gòu)造返回結(jié)果。
接著我們基于Processor接口,實現(xiàn)三個具體的處理器:
我們處理器就搞定了,等著流程引擎來喚起他們吧!
3.流程引擎設(shè)計
下面我們來看流程引擎的設(shè)計,如下圖標(biāo)識,可以把這些箭頭的控制理解為流程引擎。流程引擎的核心作用就是控制處理器按照指定順序執(zhí)行:
下面是流程引擎接口:
流程引擎只有一個start接口用來啟動流程。
以下是流程引擎抽象類。抽象類除了實現(xiàn)對處理器執(zhí)行的控制外,還可以包括日志打印、異常處理等操作。
那一個流程引擎需要執(zhí)行哪些處理器呢?這由子類決定,子類通過實現(xiàn)getProcessors()抽象方法來指定使用的處理器。你看,這里又有模板模式了是不。
下面看下我們具體的引擎子類是怎樣的:
可以看到,引擎子類實現(xiàn)getProcessors()方法即可。此方法就是告訴流程引擎具體要執(zhí)行的執(zhí)行器列表及執(zhí)行順序。
如果你走讀代碼到這里,看到list里放的三個處理器名稱,你基本上就知道“用戶查詢接口”提供了怎樣的功能。這就是良好的可讀性。
試想,如果有一天,一個流程中需要新增一個邏輯,我們可以包裝一個新的處理器,然后添加到上圖中的processorList中即可。
每個接口都可以實現(xiàn)一個如上截圖的引擎子類,用以編排需要執(zhí)行的處理器。
4.主入口的改造
當(dāng)引入流程引擎后,我們的主入口(controller)就可以改造成如下這樣(我們附上和之前兩個版本的對比圖):
你可以很明顯的看到在改造之后,由于業(yè)務(wù)邏輯被內(nèi)聚到一個個處理器中,入口處的代碼變得簡單清晰。同時你再也不用害怕每次業(yè)務(wù)需求都要改這個類,從而變得的膨脹不堪。
5.流程引擎設(shè)計一覽
我們已經(jīng)看到了整個流程引擎的實現(xiàn)過程。最后我們再用一張類圖來一覽整個設(shè)計,相信會幫助你更好地了解這種設(shè)計方法:
今日總結(jié)
今天,我們正式進入到【成為工程師】的細節(jié)內(nèi)容。我們提到,一個工程師最基礎(chǔ)的能力就是搭建系統(tǒng)。而一個系統(tǒng)要搭建得好,首先就要有一個好的系統(tǒng)框架。
我們先是通過一個反例來說明了典型的瀑布式編碼存在的問題。繼而講了通過模板模式和流程引擎兩種設(shè)計方式來優(yōu)化瀑布式設(shè)計。以此讓系統(tǒng)的擴展變得”容易、安全且規(guī)范“。
對于流程引擎,我們只是給出了一種最最基礎(chǔ)的實現(xiàn)方式而已,但是對于很多系統(tǒng)來說,這么設(shè)計已經(jīng)足夠了。事實上,真正強大的流程引擎還包括【分支循環(huán)】【異步化】【可視化界面管理】等各種高階功能,你可以自己做一些了解。
不過,流程引擎的選擇需要結(jié)合實際情況,不然也會引入額外的復(fù)雜度。
建議你收藏這篇文章,當(dāng)你碰到系統(tǒng)設(shè)計問題的時候,可以回頭來看看,相信可以幫助到你。
下一章我們會接著講系統(tǒng)設(shè)計方面的問題,來講講一個系統(tǒng)要如何分層。
加油吧,未來的架構(gòu)師們!
本文轉(zhuǎn)載自微信公眾號「 CodingBetterLife」,作者「 趙志強 」,可以通過以下二維碼關(guān)注。
轉(zhuǎn)載本文請聯(lián)系「 CodingBetterLife」公眾號。