你想成為一名架構師,對嗎?別對我撒謊,我知道你想成為架構師。即使你不想,你還是想成為一名更好的開發(fā)者。否則,你就不會花時間閱讀這篇文章
這種態(tài)度值得贊賞。畢竟,我們都希望在自己所從事的領域變得更好,即使不能稱為最好。我在這里就是為了幫助你實現這一目標。
那么,你如何成為一名架構師呢?當然是通過學習所有的架構!顯然這不現實。你不需要知道所有的架構。你也不需要對所有的架構都有經驗。但是,至少了解最流行的幾種架構 ,比如 N-Layered、DDD、Hexagon、Onion 和 Clean 架構;了解它們的歷史、用途以及它們之間的區(qū)別,無疑會讓你在與其他開發(fā)者的比較中脫穎而出。
希望你感興趣,讓我們開始吧。
一切始于何處?
回到那些美好的過去,根本沒有架構的概念。那是多么幸福的日子啊,你只需要了解 GoF 設計模式,就能自稱為架構師。
然而,隨著計算機變得更加強大,用戶的需求也增加,導致應用程序的復雜性不斷增加。
開發(fā)人員首先解決的問題是將用戶界面與業(yè)務邏輯分離。 根據不同的用戶界面框架,出現了各種類似 MVC 的模式:
這雖然有效,但效果不是很好。如果你和我一樣來自 C# 社區(qū),你可能錯誤地認為那些圖表上稱為 “Model” 的黃色方框只是 DTO(數據傳輸對象)。這完全是因為微軟的錯。他們用 ASP MVC 框架把我們搞糊涂了。可惡的微軟!
實際上,在這里,“Model” 代表的是領域模型,也就是業(yè)務邏輯,在任何應用程序中都非常關鍵。
你能猜到上述三個組件中哪個引起的問題最多嗎?視圖只是簡單的圖像和按鈕,控制器充當中間人,而所有的復雜性都集中在模型中。
那個時期,GoF 設計模式已經不夠用。因此,新的想法必須出現。我們如何處理復雜性呢?沒錯!分而治之。 我們已經在 MVC 中這樣做過了,所以讓我們再次這樣做。
2002 年:N-Layered(N 層架構)
理想的架構并非一蹴而就。就像所有事物一樣,它是通過嘗試和錯誤發(fā)展而來的。
那位開創(chuàng)軟件開發(fā)架構并對接下來的幾代開發(fā)者產生影響的人叫 Martin Fowler。他的觀點是:
于是他們開始行動。
他發(fā)表了《企業(yè)應用架構模式》一書,其中描述了 N 層架構。
這個想法很簡單,就是將所有相關的代碼分組并將其稱為不同的層 。
但是,還有更多的事情要做。Fowler 知道不一致的危害有多大。因此,為了避免我們自己給自己惹麻煩,他試圖給出一些限制和指導:
你可以按照自己的方式為各個層命名。
你可以根據需要設置任意多的層。
你可以在層之間添加新的層。
你可以在同一層中擁有多個組件。
只需確保層之間存在明確的層級關系,以便它們按順序相互引用。
這些規(guī)則不僅幫助開發(fā)人員擺脫了代碼重復,而且最終能幫助他們構建代碼。
盡管這些規(guī)則相當靈活,但在實踐中,對于大多數項目來說,3 個層已經足夠了。
用戶界面(UI):負責與用戶進行交互。
業(yè)務邏輯層(BLL):表示業(yè)務概念。它定義了應用程序的行為,使其與其他應用程序獨特區(qū)別開來。
數據訪問層(DAL):在內存中持久化數據并維護應用程序的狀態(tài)。
在這里,我們明確將業(yè)務邏輯與用戶界面分離開來。數據庫的重要性與業(yè)務規(guī)則相當,因此它有自己的層次。實際上,所有外部技術也可以放在這最后一層。一切都按照書中所說的進行。
如果你對那些彩色矩形和箭頭的意義感到困惑,不用擔心,很簡單。這些層只是解決方案中的項目,箭頭表示它們之間的依賴關系。
分離并不一定要通過項目進行物理上的分離,而可以通過文件夾進行邏輯上的分離。你也可以將兩種方法結合起來,使用最適合你的方式。
文件夾和項目之間的區(qū)別很大。 實際上,項目能夠幫助你更好地控制依賴關系。而使用文件夾時,你可能甚至不會意識到某個層開始使用另一個層的組件。另一方面,如果項目過多,代碼會變得更加脆弱和難以維護。
請記住,這并沒有嚴格的規(guī)定。你可以根據實際情況選擇適合你的方式。這總是一個可靠性和復雜性之間的權衡。我在這里的建議是,除非確實需要,不要創(chuàng)建過多的項目 ,一個項目對應一個層已經足夠了。
每個層通過其 API 調用下一層,通常以接口的形式表示。每個類的訪問修飾符和這些層同樣重要:
現在這對你來說可能顯而易見,但那只是因為你沒有經歷過真正困難的時刻。使用總是容易的,發(fā)明卻很難。
2003 年:DDD(領域驅動設計)
在 2003 年,來自波士頓的一位年輕開發(fā)者 Eric Evans 發(fā)表了他自己的書《領域驅動設計:軟件核心復雜性應對之道》,這本書至少讓 Martin 感到非常傷心。
實際上,DDD 是一個獨立的主題,需要在自己的系列文章中詳細描述,所以我們現在不會展開介紹,只關注它所引入的所有架構變化。
Evans 贊同 Fowler 的所有觀點,即項目的依賴關系應該是單向的。然而,他也提到低層模塊可以調用上層模塊,前提是不違反依賴關系的方向規(guī)則。這可以通過回調、觀察者模式等方式實現。
他還注意到控制器具有過多的邏輯,于是將其移至另一個稱為應用層的層級中。我們開始看到用例的雛形,但尚未完全發(fā)展起來。
然而,Evans 所做的最重要的事情是說 “忽略數據庫,業(yè)務邏輯更重要 ”。他說了這句話,然后卻沒有采取實質性的行動。是的,是的,我知道……DDD 等等。然而,從架構的角度來看,他并沒有做出太多改變。
在他的架構中,定義了以下層級:
表示層(Presentation Layer):負責與用戶進行交互。
應用層(Application Layer):協調任務并將工作委派給領域對象。
領域層(Domain Layer):代表業(yè)務概念。它決定了應用程序要做什么,并使其與其他應用程序獨特區(qū)別開來。
基礎設施層(Infrastructure Layer):在內存中持久化數據并維護應用程序的狀態(tài)。
你可以看到,他進行了一些重命名。
用戶界面(User Interface)意味著你有用戶,但并不總是這樣。有時它是針對用戶的圖形用戶界面(GUI),有時是針對開發(fā)人員的命令行界面(CLI),而通常它是針對程序的應用程序編程接口(API)。表示層(Presentation Layer)只是一個更通用和合適的名稱。
業(yè)務邏輯(Business logic)對一些開發(fā)人員來說很令人困惑,尤其是那些根本沒有做業(yè)務的開發(fā)人員,因此引入了一個新名稱 —— 領域(Domain)。
數據庫并不是我們使用的唯一外部工具,所以所有的電子郵件發(fā)送器、事件總線、SQL 和其他瑣碎的東西都被移動到了基礎設施層。
基本上就是這樣。在這里進行了一些重命名,再加上新增了一個層級。我們在該領域付出了很多努力。但這仍然是相同的架構,具有相同的依賴關系。要是他當時知道依賴反轉原則就好了。
2005 年:六邊形架構(Ports and Adapters)
以前,模塊必須引用行中的下一個模塊。隨著依賴反轉原則的發(fā)現,一切都改變了。
這對于軟件開發(fā)人員來說是一個難得的機會。我們終于學會了如何控制依賴關系的方向,將其指向我們希望的方式!這意味著業(yè)務邏輯不再引用數據訪問層。如果你想知道為什么這是可能的,以及接口與此有何關系,你可以在這里找到答案:
https://medium.com/@iamprovidence/from-3-layered-to-ddd-architecture-in-one-step-f3de204bec2e
第一個看到這個潛力的人是 Alistair Cockburn。這家伙吸很嗨,畫了一個六邊形,試圖召喚撒旦,等等。我不需要告訴你,你自己更了解在搖滾派對上發(fā)生的情況。沒什么特別的,有一天你喝了很多酒,第二天早上醒來時帶著嚴重的宿醉,你意外地發(fā)現了一種新的架構。
Alistair 對矩形感到厭倦,于是他畫了一個六邊形,為每個東西想出了兩個名字,試圖讓它們變得神圣起來。但不要被嚇到,我的開發(fā)伙伴。實際上,這種架構并不比 N 層架構復雜:
這其中有很多旋轉和移動,讓我們看看實際發(fā)生了什么。
Cockburn 實現了 Evans 的夢想。現在,Domain 是系統(tǒng)的核心組件,不僅在言辭上,而且在行動上也是。它不再引用其他項目。
為了強調它真正是核心,業(yè)務邏輯被重命名為核心(Core)。
基礎設施模塊被分成兩部分 —— 抽象(接口)和實現 。抽象成為業(yè)務邏輯的一部分,并被重命名為端口(ports)。實現部分保留在基礎設施層中,現在它們被稱為適配器(adapters)。實際上,UI 和數據庫(DB)位于相同的框架層,因此它們經歷了相同的命運。
將基礎設施的接口放在業(yè)務邏輯中,使 Domain 變得自治且無依賴。結果,業(yè)務邏輯可以在任何環(huán)境中使用任何工具。想要更改數據庫?只需更改實現部分,實現所需的適配器,并將其 “插入” 到可用的端口中。
任何適配器的更改(數據庫、電子郵件發(fā)送器、UI)都不會影響業(yè)務邏輯。接口保持不變。
每個組件都可以單獨部署。如果更改數據訪問,只需重新構建數據訪問部分。如果只更改 UI,只需更改 UI 部分。
由于可以單獨部署模塊,這意味著它們可以單獨開發(fā)。
只有優(yōu)點。
我忘了提到,調用我們系統(tǒng)的適配器稱為主要適配器(驅動)。被我們系統(tǒng)調用的適配器稱為次要適配器(被驅動)。雖然這不重要,但了解這一點會讓你聽起來很博學。
就解決方案結構而言,以下對我來說效果最好:
再次強調,文件夾與項目是你自己決定的事情。
只需按照引用關系,并確保它們不會跨越不應跨越的地方:
2008 年:洋蔥架構
這個故事有點令人毛骨悚然,所以做好準備吧。
Jeffrey Palermo。這是一個充滿悲傷和黑暗的故事,講述了一個男孩童年時被洋蔥的殘忍思考所困擾的悲傷故事。隨著他的成長,他心中燃燒著一種憤恨,懷著復仇的承諾。
而相信我,他對這個承諾始終如一。這個小洋蔥讓全世界數百萬開發(fā)人員哭泣,向他們的母親尋求安慰。
這種架構從端口和適配器中得到了很多提升。它仍然涉及依賴反轉。它按照抽象和實現分割代碼。端口仍然是業(yè)務邏輯的一部分。只不過這次,Palermo 從 Evans 的模式中添加了應用層,該層也可以包含一些端口。
這種架構面臨的最大挑戰(zhàn),也是導致混淆的原因,是模塊之間的依賴關系。
然而,規(guī)則很簡單:任何外層只能且只能依賴于內層 。
不夠簡單,對吧?我也是這么想的。那么,讓我們來剖析一下這個洋蔥。
Domain 位于中心。它內部沒有內層,因此不應依賴于任何其他層。
應用層僅包裹領域,所以它只應該依賴于 Domain。
基礎設施層和展示層位于同一級別,它們不能相互依賴,但可以依賴于應用層和 Domain,因為所有所需接口都在其中定義。
你還可以看到它擁有 DDD 架構中的所有模塊,但以不同的方式處理它們。這實際上非常重要!關鍵在于將很少發(fā)生修改的組件放在中間,并將頻繁發(fā)生修改的組件放在邊緣。
應用層或任何其他層的更改不會影響領域,只會影響相關的層。 只有當業(yè)務邏輯發(fā)生變化時,Domain 才會發(fā)生變化,而這種情況無論如何都會影響整個系統(tǒng)。
這是理論上的情況。在實踐中,你的組合根(Main() 函數,在其中注冊所有依賴項并將模塊組合在一起)將成為展示層(ASP、WPF、CLI)的一部分,因此圖表將如下所示:
對你來說這個看起來熟悉嗎?這就是 N 層架構,只是組件的順序不同。
不管它的外觀如何,無論是六邊形、端口還是洋蔥,你的最終目標是將依賴關系以無環(huán)圖或樹形結構的形式呈現出來。
2012 年:清潔架構
有個名叫 Bob 的人, 他是最優(yōu)雅的程序員, 他的敏捷之舞和完美架構, 讓你的代碼嶄新光亮。
我是說,要講述關于架構的文章,就不能不提到 Robert C.Martin。
他看到了關于架構的熱潮,并決定加入其中。Martin 了解開發(fā)者的主要秘密,因此他毫不掩飾地借用別人的想法,并將其稱為自己的。
開個玩笑,如今很少有原創(chuàng)的想法,大家都在相互借鑒。讓我們看看 Martin 在這里帶來了什么:
我們可憐的 Domain 再次改名,現在稱為實體(Entities)。但那不僅僅是改名而已。它意味著你不會再有領域服務和貧血模型,而是擁有數據和行為的豐富類。
倉儲接口和其他端口從領域層移到應用層。而應用層也得到了一個更合適的名稱 —— 用例(Use Cases)。
展示層和基礎設施層保持不變。然而,Martin 還在頂部添加了一個額外的層,其中包括框架、DLL 和其他外部依賴。這并不意味著你的數據庫將引用實體,它只是防止內部層引用外部工具。
再次強調,沒有嚴格的規(guī)定。你可以在任何級別添加任意多的層。所以如果你想為領域服務定義一個層,你可以這樣做。
Martin 還在架構旁邊畫了一個小圖。
圖中顯示用戶通過觸發(fā)控制器的端點與系統(tǒng)進行交互,控制器調用一個用例,然后通過展示器返回數據(黑線)。用例可以通過接口調用任何它所需的端口(綠線)。而實際的實現則位于外層(橙線)。
圖表試圖強調執(zhí)行流程(虛線)并不總是與依賴關系方向(實線)相對應,這就是依賴倒置原則。
基本上,它再次強調了控制反轉的使用。在我們討論端口和適配器時,你已經見過這一點。
通常在 ASP 中,我們沒有單獨的展示器組件。這也由控制器來完成。因此,整個圖表可以在代碼中表示為:
class?OrderController?:?ControllerBase,?IInputPort,?IOutputPort { ????[HttpGet] ????public?IActionResult?Get(int?id) ????{ ????????_getOrderUserCase.Execute(id); ????????return?DisplayOutputPortResult(); ????} }
其他形式的隔離
所有這些架構都旨在通過分離責任來將一個代碼從另一個代碼中隔離出來。然而,還有其他形式的隔離:垂直切片、有界上下文、模塊、微服務等。這些方法的目標是根據功能來劃分代碼。
有些人不認為它們是 “真正的” 架構方法,而有些人則認為它們是。這取決于你的觀點。最終,它們可能會發(fā)展到一種程度,在那個程度上它們可能會使用上述任何一種架構風格,甚至是它們的組合:
結論
在本文中,我們討論了 N-layered、DDD、六邊形、洋蔥和清潔架構。這些只是眾多存在的架構中的一部分,是一些比較出名的架構。你可能還聽說過 BCE、DCI 等。
盡管在細節(jié)上可能存在一些差異,但所有這些架構實際上是非常相似的。它們都有著相同的目標 —— 分離責任 。它們通過將代碼分割成不同的層來實現這一目標。唯一的區(qū)別在于定義了哪些組件以及這些層之間存在什么樣的依賴關系。
現在你對整個情況有了全面的了解,我強烈鼓勵你再次閱讀本文。自己明白不同架構之間的差異。你還可以嘗試自己動手進行項目實踐。編寫一些帶有接口的類,關注項目之間的引用關系,接口和實現的放置位置,以及所使用的訪問修飾符。
希望從現在開始,每當你創(chuàng)建一個類、審查一個 Pull Request,或者與你的同事進行討論時,你都能有意識地思考并質疑這些事情。
編輯:黃飛
?
評論
查看更多