「整潔架構(gòu)」實戰(zhàn) MVC 架構(gòu)重構(gòu)到整潔架構(gòu)

軟件架構(gòu)介紹

什么是軟件架構(gòu)

我們先來看看維基百科對 軟件架構(gòu) 的定義, 軟件架構(gòu)是有關(guān)軟件整體結(jié)構(gòu)與組件的抽象描述,用于指導大型軟件系統(tǒng)各個方面的設(shè)計。簡短卻又看不太明白的一個定義,確實很抽象,第一眼看上去相信大多數(shù)都會有這么個感覺。自己三五年的工作經(jīng)驗,架構(gòu)設(shè)計對自己來說還不急,自己再多學點XX技術(shù),學的差不多了,再開始學架構(gòu)設(shè)計吧。這種思維模式其實是不對的,往大了說,淘寶的架構(gòu)需要架構(gòu)設(shè)計,往小了說一個畢業(yè)生的畢業(yè)設(shè)計也需要架構(gòu)設(shè)計,架構(gòu)的核心不在于自己會多少高大上的技術(shù),架構(gòu)中用了多少技術(shù),軟件架構(gòu)的終極目標是 用最小的人力成本來滿足構(gòu)建和維護該系統(tǒng)的需求。 所以在平時的開發(fā)過程中其實會有很多實踐機會來鍛煉你的架構(gòu)設(shè)計能力,哪怕是在一次日常的 code review 中。

架構(gòu)設(shè)計的價值

軟件系統(tǒng)可以通過行為架構(gòu)兩個緯度來衡量實際價值。正常情況下程序員應(yīng)該確保自己的系統(tǒng)在這兩個維度上的實際價值都能長時間維持在比較好的狀態(tài) 。但事實上,隨著項目不斷演進,最初的架構(gòu)設(shè)計往往會為各種“行為”操作作出妥協(xié),最終導致了軟件不再“軟”,使得系統(tǒng)的價值最終趨近于零。

行為價值
軟件系統(tǒng)的行為是其最直觀的價值維度。程序員的日常工作就是根據(jù)客戶提供的需求,編寫需求文檔,然后按照需求文檔將需求轉(zhuǎn)換成可以工作的系統(tǒng),從而給系統(tǒng)的使用者創(chuàng)造價值。當系統(tǒng)遇到問題時,那么就進行調(diào)試,解決問題。這其實就是大多數(shù)程序員的全部工作,按照需求文檔,編寫代碼,修復bug... 對于一個普通的程序員來說這又有什么不對呢?一個軟件由最開始比較完美的架構(gòu)設(shè)計,慢慢的會為了功能作出各種妥協(xié)讓步,從一個“軟”件,慢慢的變硬,導致最后有了新的變更就變得痛不欲生,只能加班加點。造成這樣的原因其實就是因為我們僅僅只是按照需求文檔往系統(tǒng)上堆功能,遇到bug了打打補丁,讓程序能夠正常運行起來就好,最大化眼前的收益,卻不知是在一步一步的侵蝕最初的架構(gòu)。

架構(gòu)價值
軟件系統(tǒng)的第二個價值維度就體現(xiàn)在軟件這個詞上 : software。 “ware” 的意思是“產(chǎn)品”,而“ soft'’的意思,不言而喻,是指軟件的靈活性。軟件發(fā)明的目的,就是讓我們可以以一種靈活的方式 來改變機器的工作行為 。 對機器上那些很難改變的工作行為,比如內(nèi)存條,CPU,我們通常稱之為硬件 ( hardware ) 。為了保證架構(gòu)的價值,我們就要持續(xù)的保證軟件系統(tǒng)一直是“軟”的狀態(tài),也就是說軟件應(yīng)該是對外開放的,易于被修改的,在有需求增加或變更時,對應(yīng)的軟件必須是可以簡單而方便的實現(xiàn)。不應(yīng)該導致代碼變更的成本與其實現(xiàn)功能改變不成比例。比如在一個系統(tǒng)中支持了微信支付用了3人天,后續(xù)新增了支付寶支付卻用了5人天,而且還需要改大量微信支付的代碼,這是不能接受的。因為對于客戶來說,他們提出的系列的變更需求的范疇都是類似的,因此它們的成本也應(yīng)該是相同的,但是對于開發(fā)者來說,系統(tǒng)持續(xù)不斷的需求變更導致他們慢慢的忽視了軟件架構(gòu)的設(shè)計,從而導致系統(tǒng)變得越來越難維護,最后系統(tǒng)將變得無法修改。

成為一個合格的開發(fā)者,思考架構(gòu)設(shè)計的行為價值最基本的,更加重要也必須重視的是在行為過程中必須要要高度警惕架構(gòu)價值,思考如何才能讓軟件繼續(xù)“軟”下去。

從代碼整潔到模塊設(shè)計

編寫整潔的代碼

Bob 大叔的系列著作其實就是在引導一個剛?cè)腴T的新手該如何慢慢進階成一名靠譜的程序員。其中《代碼整潔之道》教會我們?nèi)绾螌懗鲆鬃x、可擴展 、可維護、可重用的代碼,打好基本功?!洞a整潔之道:程序員的職業(yè)素養(yǎng)》教會我們怎樣變成一個有修養(yǎng)的程序員。而《架構(gòu)整潔之道》則是在技術(shù)上的一個進階,從微觀(代碼層面)到宏觀(架構(gòu)層面),介紹了軟件開發(fā)的三種編程范式,設(shè)計原則以及提出了整潔架構(gòu)這一架構(gòu)模式。
那么第一步,該如何編寫整潔的代碼呢?《代碼整潔之道》引用了一個軟件開發(fā)中的 5S 原則:

  • 整理:命名的規(guī)范
  • 整頓:把你的代碼放在它應(yīng)該在的位置
  • 清楚:整潔的代碼
  • 清潔:代碼風格、實踐手段
  • 身美:不斷改進

量化出來,在編寫代碼的時候雖然沒有固定的標準,但有一個大體的規(guī)范:

  • 可讀性永遠是最重要的(想想你被迫看別人代碼的樣子)
  • 有意義的命名(變量名,方法名,函數(shù)名,類名,包名)
  • 只做一件事(降低耦合,提高可讀性)
  • 減少依賴關(guān)系(提高程序健壯性)
  • 避免不必要的重復代碼
  • 避免不必要的注釋,盡量對代碼段進行自解釋
  • 通過所有測試

本 chat 的側(cè)重點會在設(shè)計原則和整潔架構(gòu)上,整潔的代碼這塊具體的細節(jié)就不贅述了,想要了解更多可以看《代碼整潔之道》和《重構(gòu)》。

再識 SOLID 原則

想要構(gòu)建一個好的軟件系統(tǒng),編寫整潔的代碼尤為關(guān)鍵,就好比建筑需要好的轉(zhuǎn)頭一樣。有了結(jié)實的轉(zhuǎn)頭才能繼續(xù)修建堅固的墻和各種套間,最終構(gòu)建成高樓大廈。S.O.L.I.D 原則則是告訴我們?nèi)绾螌?shù)據(jù)和函數(shù)組合成類,以及如何將這些類連接成程序(類表一組數(shù)據(jù)和函數(shù)的組合),S.O.L.I.D 適用于架構(gòu)中的中層組件和模塊,其目的:

  • 構(gòu)建出易于拓展的軟件,接受變化,使的軟件可以很容易的進行變更
  • 抽象化組件的設(shè)計,使的軟件可以更好的表達自己,便于理解
  • 構(gòu)建可復用的組件
Single Responsibility Principle 單一職責原則

SRP 原則在日常開發(fā)中被提到的應(yīng)該也是最多的,大多數(shù)人對它的理解都是:每個類應(yīng)該只做一件事,其實這是一個比較模糊的說法。比較準確的含義應(yīng)該是:

任何一個軟件模塊應(yīng)該只對某一類行為者負責

其中:

  • 軟件模塊指的是一個源代碼文件(一組緊密相關(guān)的函數(shù)和數(shù)據(jù)結(jié)構(gòu))
  • 行為者指的是只有一個或多個有共同需求的用戶

舉個書中的例子,在一個工資管理系統(tǒng)中有一個 Employee

UTOOLS1590585594117.png

這個類中的三個函數(shù)很明顯違背的 SRP 原則,三個函數(shù)也不應(yīng)該在 Employee 中,計算薪水、計算工時和保存的操作應(yīng)該分別在財務(wù)、人力和 DBA 中去做。那針對這種情況,該怎么做職責分離呢?
最簡單粗暴的方式直接把數(shù)據(jù)和函數(shù)分離
UTOOLS1590586003097.png

當然也可以使用 Facade 模式,將具體的實現(xiàn)邏輯隱藏,對外只暴露簡單的api
UTOOLS1590586261068.png

Open Closed Principle 開閉原則

OCP 原則最能體現(xiàn)出一個系統(tǒng)拓展性的能力,當新增一個需求時,能不能做到最小化甚至不需要對先有的代碼做任何改動,一個良好的軟件設(shè)計應(yīng)該是易于拓展,同時抗拒修改。而想要做到這一點,就必須遵守 依賴反轉(zhuǎn)原則,多變的一方應(yīng)該依賴于穩(wěn)定的一方,穩(wěn)定的一方不應(yīng)該依賴多變的實現(xiàn)。這樣就可以在新增功能的同時,減少對內(nèi)的修改(較穩(wěn)定端的修改)。

Liskov Substiution Principle 里氏替換原則

LSP 指的是一種可替換性:如果對于每個類型為 S 的對象 o1 都存在一個類型為 T 的對象 o2,能使操作 T 類型的程序 P 在適應(yīng) o2 替換 o1 時行為保持不變,我們就可以將 S 稱為 T 的子類型。它的主要作用就是用來指導繼承關(guān)系和接口及其實現(xiàn)的原則。

Interface Segregation Principle 接口隔離原則

客戶端不應(yīng)該被迫依賴于它們不用的接口,如果一個接口包含了過多的方法,而不同的子類只需要實現(xiàn)部分的方法,那么就應(yīng)該通過分離接口將其拆分,不同的子類實現(xiàn)不同的接口,實現(xiàn)對應(yīng)的功能。在一般情況下,任何層次的軟件設(shè)計如果依賴于不需要的東西,都會是有害的。

Dependency Inversion Principle 依賴反轉(zhuǎn)原則

DIP 原則是非常重要的編碼思維。從組件的設(shè)計到很多架構(gòu)設(shè)計中 DIP 都扮演著非常重要的角色。DIP 指程序要依賴于抽象接口,不要依賴與具體實現(xiàn),高層模塊和底層模塊都應(yīng)該依賴于抽象,也就是所謂的面向接口編程。面向接口編程的好處就是在設(shè)計的時候可以忽略具體實現(xiàn),設(shè)計定義抽象邏輯,這樣設(shè)計出來的組件才會更加穩(wěn)定(在面向?qū)ο笳Z言中,接口相對具體類而言,改動幾率小,更加穩(wěn)定)。所以為了追求架構(gòu)上的穩(wěn)定,就必須要多使用穩(wěn)定的接口,少依賴于多變的具體實現(xiàn)。DIP 的幾個原則:

  • 在代碼中多使用抽象接口,避免使用多變的具體實現(xiàn)類。對象的創(chuàng)建一般使用工廠模式創(chuàng)建
  • 不要在具體實現(xiàn)類上創(chuàng)建衍生類
  • 不要覆蓋(override)包含了具體實現(xiàn)的函數(shù)(調(diào)用包含了具體實現(xiàn)的函數(shù)通常意味著引入了源代碼級別的依賴)需要 override 時一般創(chuàng)建抽象函數(shù),子類重寫該抽象函數(shù)
  • 避免在代碼中寫入任何與具體實現(xiàn)相關(guān)的名字或其它容易變動的事物的名字
UTOOLS1590589064634.png

中間的曲線代表了軟件架構(gòu)中的抽象層與具體實現(xiàn)層的邊界。所有跨越這個邊界源代碼級別的依賴都應(yīng)該是單向的,即具體的實現(xiàn)層依賴于抽象層。

初識整潔架構(gòu)

六邊形架構(gòu)(Hexagonal Architecture)、DCI(Data, Context, Interactive)架構(gòu),BCE(Boundary,Controller,Entity)架構(gòu)

六邊形架構(gòu)

六邊形架構(gòu)是 Alistair Cockburn 在2005年提出,為了解決傳統(tǒng)的分層架構(gòu)帶來的問題,六邊形架構(gòu)也算是一種分層架構(gòu),只不過是從內(nèi)到外的分層,而不是上下分層。傳統(tǒng)的三層架構(gòu)(表示層,業(yè)務(wù)邏輯層,數(shù)據(jù)訪問層)存在的一些問題:

  • 程序的核心邏輯可能會散落在不同的層里面,導致后續(xù)如果需要替換某一層難度會非常大
  • 對核心邏輯的測試非常困難
  • 核心邏輯會依賴到第三方的具體依賴,導致業(yè)務(wù)和具體的技術(shù)強綁定,變動困難
UTOOLS1590820385894.png

六邊形架構(gòu)又稱為端口-適配器,六邊形架構(gòu)將系統(tǒng)分為內(nèi)部和外部,內(nèi)部代表了應(yīng)用的核心業(yè)務(wù)邏輯,外部代表應(yīng)用的驅(qū)動邏輯、基礎(chǔ)設(shè)施或其他應(yīng)用。六邊形架構(gòu)的幾個特點:

  • 關(guān)注點分離:軟件的價值在與它的業(yè)務(wù)價值,所以重心放在業(yè)務(wù)邏輯上,將業(yè)務(wù)邏輯和外部的驅(qū)動(具體技術(shù))分離開來,業(yè)務(wù)和技術(shù)是無關(guān)的,這樣對于業(yè)務(wù)邏輯而言會更加穩(wěn)定,也更容易測試
  • 外部可替換:一個端口對應(yīng)多個適配器,它體現(xiàn)了對外部的抽象。內(nèi)部不關(guān)心外部如何使用端口,從一開始就要假定外部使用者是可替換。比如對于數(shù)據(jù)持久化,對于業(yè)務(wù)邏輯來說是不需要知道自己用的是什么,只需要提供端口,然后會有對應(yīng)適配器進行實現(xiàn)提供持久化功能
  • 依賴倒置:依賴倒置是六邊形架構(gòu)的基礎(chǔ),為了保證內(nèi)部業(yè)務(wù)邏輯的穩(wěn)定性,就必須做到不讓內(nèi)部依賴與外部組件,只能外部依賴與內(nèi)部,這樣外部才是可被替換的,實現(xiàn)這一點就需要使用依賴倒置,由驅(qū)動者適配器將被驅(qū)動者適配器注入到應(yīng)用內(nèi)部,端口的定義在應(yīng)用內(nèi)部,而具體的技術(shù)實現(xiàn)是由適配器完成
DCI 架構(gòu)

DCI 是對象的 Data 數(shù)據(jù)、 對象使用的 Context 場景對象的 Interaction 交互行為三者簡稱, DCI 的重點是在不同的場景下的交互行為,是面向?qū)ο笾袪顟B(tài)和行為的一種范式設(shè)計。傳統(tǒng)的 MVC 架構(gòu)雖然結(jié)構(gòu)簡單清晰,但是對于業(yè)務(wù)邏輯的肢解防止隨著項目的復雜,會越來約混亂,跟蹤代碼時會非常困難。DCI 最突出的亮點就是 基于用例驅(qū)動設(shè)計,這樣更加符合用戶的心智模型,代碼即需求,基于用例的代碼編寫對與程序員后續(xù)理解起來也更方便。

整潔架構(gòu)解析

整潔架構(gòu)是將六邊形架構(gòu)、DCI架構(gòu)和BCE架構(gòu)的設(shè)計理念做了一個綜合。這些架構(gòu)都有一個共同的設(shè)計目標:按照不同的關(guān)注點對軟件進行分割,至少有一層是包含軟件的核心邏輯。具備的特點:

  • 獨立于框架:整個系統(tǒng)的架構(gòu)不會依賴于具體的架構(gòu),架構(gòu)可以被當作工具來使用,但是不會讓系統(tǒng)來是適應(yīng)架構(gòu)
  • 可被測試:系統(tǒng)的業(yè)務(wù)邏輯可以脫離UI、數(shù)據(jù)庫、Web等其它外部元素獨立測試
  • 獨立于UI
  • 獨立于數(shù)據(jù)庫
  • 獨立與其它任何第三方依賴
UTOOLS1590824388487.png
依賴規(guī)則

圖中的同心圓代表軟件系統(tǒng)中的不同層次,越靠近中心,其所在的軟件層次就越高。外層圓代表的是機制,內(nèi)層圓代表的是策略。源碼中的依賴關(guān)系必須指向同心圓的內(nèi)層,即底層機制指向高層策略。內(nèi)層圓中的代碼不能涉及外層圓中的代碼,尤其是內(nèi)層圓中的代碼不應(yīng)該引用外層圓代碼中的變量,方法等。

整潔架構(gòu)的幾個概念

業(yè)務(wù)實體
業(yè)務(wù)實體這一層中封裝的是整個系統(tǒng)的關(guān)鍵業(yè)務(wù)邏輯, 一個業(yè)務(wù)實體既可以是 一個帶有方法的對象,也可以是一組數(shù)據(jù)結(jié)構(gòu)和函數(shù)的集合。無論如何,只要它能 被系統(tǒng)中的其他不同應(yīng)用復用就可以。

用例
軟件的用例層中通常包含的是特定應(yīng)用場景下的業(yè)務(wù)邏輯,這里面封裝并實現(xiàn)了整個系統(tǒng)的所有用例 。 這些用例引導了數(shù)據(jù)在業(yè)務(wù)實體之間的流入/流出,并指揮著業(yè)務(wù)實體利用 其中的關(guān)鍵業(yè)務(wù)邏輯來實現(xiàn)用例的設(shè)計目標。用例這一層相對于 業(yè)務(wù)實體 來說是外層,所以對于這一層發(fā)生的變化也不應(yīng)該影響到內(nèi)層,所以 業(yè)務(wù)實體層 也不應(yīng)該依賴用例層。同時 用例層 相對于外部框架/基礎(chǔ)設(shè)施來說是內(nèi)層,所以用例層也不應(yīng)該直接依賴于它們。

接口適配器
軟件的接口適配器層中通常是一組數(shù)據(jù)轉(zhuǎn)換器,它們負責將數(shù)據(jù)從對用例和業(yè) 務(wù)實體而言最方便操作的格式,轉(zhuǎn)化成外部系統(tǒng)(譬如數(shù)據(jù)庫以及 Web)最方便操作的格式。例如,這一層中應(yīng)該包含整個GUI MVC框架。展示器、視圖、控制器都屬于 適配器,對于業(yè)務(wù)的處理,需要從控制器傳遞給用例層,用例層作為業(yè)務(wù)邏輯處理的入口,最終返回結(jié)果為控制器。

這一層也會負責將數(shù)據(jù)格式從用例和實體層最方便的格式轉(zhuǎn)換為所使用的持久化框架最方便的格式,也就是說在這一層會將業(yè)務(wù)實體的數(shù)據(jù)格式轉(zhuǎn)換為持久化數(shù)據(jù)庫所需要的 PO 格式,這個轉(zhuǎn)換是在 接口適配器層,這樣未來一旦換了持久化框架,那么也不會影響到內(nèi)層邏輯。

同時,這一層也會負責將來自外部的數(shù)據(jù)格式轉(zhuǎn)換為系統(tǒng)內(nèi)部業(yè)務(wù)實體所需要的格式。比如在一個微服務(wù)架構(gòu)的系統(tǒng)中,在A服務(wù)中調(diào)用B服務(wù),那么這個過程就在 接口適配器層 中,并且會將返回的數(shù)據(jù)格式轉(zhuǎn)換成當前服務(wù)業(yè)務(wù)實體需要的格式。

跨越邊界

圖中每一個同心圓都表示一個邊界。源代碼級別的依賴關(guān)系一定是外層依賴與內(nèi)層。層次越往內(nèi),那么其抽象和策略的層次越高,最內(nèi)層的圓中包含的是最通用的,最高層的策略,最外層的圓包含的是最具體的實現(xiàn)細節(jié)。那么出現(xiàn)內(nèi)層一定會調(diào)用外層這種情況,比如用例層需要調(diào)用網(wǎng)關(guān)適配器,進行數(shù)據(jù)的持久化,按照整潔架構(gòu)的原則是不允許這樣操作,這個時候就需要采用依賴反轉(zhuǎn)原則來解決這種相反性。在用例層定義需要持久化的策略,一般是一個接口,在網(wǎng)關(guān)適配器中實現(xiàn)這個接口調(diào)用具體的持久化框架進行功能實現(xiàn),這樣就可以避免用例層直接依賴適配器層。

插件化思維

當我們希望設(shè)計出來的系統(tǒng)的具備更好的可拓展性,可維護性,那么就必須在設(shè)計的時候具備插件化思維,抽象化使用者的場景,將這些場景進行收斂,封裝成抽象的高層策略暴露為使用者。一般情況會使用 約定/注入 方式。比如 Spring 框架給使用者提供各種前置/后置處理器策略,允許使用者通過自定義處理器實現(xiàn),然后注入到 Spring 中,Spring 根據(jù)對應(yīng)的策略處理用戶注入的事件。那么對軟件系統(tǒng)來說,用例層,業(yè)務(wù)實體層封裝的就是核心邏輯和高層策略,這些策略就可能包含了對數(shù)據(jù)持久化的策略,對第三方依賴調(diào)用時的策略,或者是提供策略,暴露給調(diào)用者,那么調(diào)用者編寫對應(yīng)符合該策略的功能代碼,那么就可以使用我們提供的內(nèi)置功能,比如SonarQube允許開發(fā)者上傳自己的代碼質(zhì)量測試實現(xiàn)。

延遲抉擇思維

在最初的架構(gòu)設(shè)計中完成了高層策略的設(shè)計后,那么對于細節(jié)的技術(shù)實現(xiàn)該怎么選擇呢?比如數(shù)據(jù)持久化,數(shù)據(jù)展示等。延遲抉擇可以幫到我們,在一個敏捷開發(fā)團隊中都會采用迭代思想來交付項目,按照精益思想,我們應(yīng)該在每個迭代中減少浪費,最大化交付價值。那么什么叫做浪費呢?在當前迭代和迭代目標不相關(guān)的都算是浪費,如果團隊做了一件事,而這件事在下個迭代才產(chǎn)生價值,那么這件事也算是浪費。那么這也是 延遲抉擇 想要表達的,如果一件事沒有到必須要做決定,必須要做的時候,那么就先擱置它,直到必須要做的時候,因為到了必須要處理的時候,對這件事的理解程度才會達到最大化。那么可以這么做的前提就是必須要做好高層策略的設(shè)計,比如數(shù)據(jù)持久化,在一開始業(yè)務(wù)可能只是需要保存簡單的數(shù)據(jù),那么這個時候是不是真的需要一個數(shù)據(jù)庫呢?一個簡單文檔存儲在當前階段是不是更能帶來價值呢?

實戰(zhàn) :MVC 架構(gòu)重構(gòu)整潔架構(gòu)

項目背景介紹

前面嘮叨了這么多理論知識,接下來會演示一個項目實戰(zhàn),一個最常見的 mvc 架構(gòu)的代碼,我們該怎么把它重構(gòu)成 整潔架構(gòu)。這個系統(tǒng)是一個比較簡單的微服務(wù)系統(tǒng),雖然麻雀雖小,但五臟俱全。(為了快速演示整個重構(gòu)的套路,所以代碼中沒有涉及到測試相關(guān)的代碼,一般情況下,在重構(gòu)前一定是需要有測試來保證整個重構(gòu)過程的。)


UTOOLS1590843356943.png

整個系統(tǒng)包含兩種角色,學生和老師

  • 學生:有兩個功能。1. 可以發(fā)表筆記;2.刪除筆記(如果該筆記是優(yōu)秀筆記,那么也會把這個狀態(tài)記錄刪除)
  • 老師:有兩個功能。1. 將學生的筆記標記為優(yōu)秀筆記,標記成功會請求 通知服務(wù),發(fā)送標記成功的通知;2. 將學生的筆記取消優(yōu)秀狀態(tài),然后請求 通知服務(wù),發(fā)送取消成功通知

MVC 版代碼和遺留問題介紹

mvc 版的代碼可以查看github。當前版本中也還有很多的壞味道和遺留的一些問題,在重構(gòu)的時候我們會一起重構(gòu);

  • 代碼中充斥著大量的 map
  • 業(yè)務(wù)邏輯和具體的技術(shù)實現(xiàn)強綁定在一起,互相依賴
  • 沒有 domain model,直接把 PO 當 domain model 用
  • API 請求和返回沒有對應(yīng)的 model

重構(gòu)手法

重構(gòu)之后的代碼查看 clean-arch分支,里面包含了每一步重構(gòu)的commit,可以 checkout 到對應(yīng)的 commit 進行查看

UTOOLS1590849608132.png

1. 分層

1.1 創(chuàng)建 adapter 和 domain
分別創(chuàng)建 adapter/inbound/rest/resources 、adapter/outbound/gateway、 adapter/outbound/persistence、domain

1.2 移動 controller
controller 內(nèi)的類移動到 adapter/inbound/rest/resources 下并按照領(lǐng)域分包

1.3 移動 repositories,feign 和 services

  • controller 中的請求入?yún)⒎庋b為對應(yīng)的 xxRequest 對象。
  • controller 中的返回值數(shù)據(jù)封裝為對應(yīng)的 xxResponse 對象。
  • repositories/ 放到 adapter/outbound/persistence/
  • feign/ 放到 adapter/outbound/gateway/
  • services/ 下的內(nèi)放到 domain/
2. 拆解 PO 和 domain model

models/ 放入到 domain 下放入對應(yīng)的領(lǐng)域包下,然后拷貝一份到 adapter/outbound/persistence/下改名為 XXPO,接著把 domain 下的對象把和持久化相關(guān)的配置全部刪除。

3. 使用 DIP 移除對持久層的直接依賴

目前 domain service 中對數(shù)據(jù)的操作還是直接調(diào)用的 jpaRepository,需要使用 DIP 進行依賴反轉(zhuǎn)。

  • adapter/outbound/persistence/ 下的 repository 更名為 repositoryVendor
  • domain/xx/ 下創(chuàng)建對應(yīng)的接口 xxRepository,將之前對直接調(diào)用 xxRepository中的方法定義在該接口中
  • adapter/outbound/persistence/xx/ 下創(chuàng)建實現(xiàn)類 xxRepositoryImpl 實現(xiàn)xxRepository對應(yīng)的功能
  • domain/xxService 中使用 DI 注入 xxRepository 解除對 jpaRepository 的直接依賴
4. 使用 DIP 移除 domain 對 gateway 的依賴

使用 DIP 原則進行依賴倒置,解除 domain 層對 gateway 層的直接依賴(Spring feign)

5. 抽取 usecase
  • 將 domain service 中的業(yè)務(wù)邏輯按需求用例梳理,放入到用例層 application/usecases/,一般情況下會將每個業(yè)務(wù)實體拆解成 editquery兩類用例,分別對外提供編輯和查詢的入口。原 controller 中對 domain service 的調(diào)用改為依賴對應(yīng)的 xxUseCase
  • domain/notification 相關(guān)代碼放入到 application/gateway/notification 下,通知的 context 在當前服務(wù)不屬于核心領(lǐng)域,相對于筆記服務(wù)是第三方服務(wù)
6. 移除代碼中存在內(nèi)層依賴外層的情況

外層可以依賴與內(nèi)層,內(nèi)層不能依賴與外層。比如在 application 層依賴了 adapter 中的 xxRequest。出現(xiàn)這種數(shù)據(jù)對象的依賴問題,可以把外層的對象里面的數(shù)據(jù)當作基本類型的參數(shù)傳遞給內(nèi)層,如果參數(shù)過多,也可以在內(nèi)層創(chuàng)建對應(yīng)的 xxDto,在外層構(gòu)造好這個 xxDto 當作參數(shù)傳遞給內(nèi)層。

重構(gòu)經(jīng)驗總結(jié)

嚴格模式

如果團隊內(nèi)的編碼水平存在明顯的差距。那么建議嚴格按照實例的架構(gòu)模式,也就是每個請求必須由 controller 進來調(diào)用 usecase,然后 usecase 在調(diào)用對應(yīng)的 domain service,即使 usecase 或 domain service 可能只是一層簡單的代理,那么也要按照這種模式寫,雖然比較啰嗦,但是長期來看,可以很好的解決代碼混亂的問題。

寬松模式

為了避免 usecase 或 domain service 只是做了一層代理,在寬松模式下,我們可以根據(jù)當下的情況進行抉擇是否需要這一層,是否可以直接跳過這一層

  • controller 中只存在最簡單的 CURD 時,那么 controller 完全可以直接注入對應(yīng)的 repository
  • 當出現(xiàn)跨上下文即有一個場景需要調(diào)用到多個 domain service 時,那么就需要創(chuàng)建對應(yīng)的 usecase,在 usecase 中完成跨上下文的操作
  • usecase 和 domain service 中出現(xiàn)的數(shù)據(jù)對象一般被命名為 xxDto,如果這個 xxDto 只在 usecase 內(nèi)用到,那么應(yīng)該放到 usecase 這一層
  • 同層之間可以互相依賴
  • 不應(yīng)該存在循環(huán)依賴
跨層數(shù)據(jù)的轉(zhuǎn)換

基于內(nèi)層不能依賴與外層的數(shù)據(jù)的原則。如果外層想要傳遞一個外層的數(shù)據(jù)對象到內(nèi)層,那么一般情況會將外層的這個數(shù)據(jù)對象拆解,把里面的變量當作參數(shù)傳遞給內(nèi)層。如果傳遞的變量個數(shù)比較多,那么就可以在內(nèi)層創(chuàng)建一個對應(yīng)的 xxDto,現(xiàn)在外層構(gòu)造這個 xxDto,然后將這個 xxDto 傳遞給內(nèi)層。

參考文獻

  • Robert C. Martin 《架構(gòu)整潔之道》,電子工業(yè)出版社 孫宇聰譯
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容