談談後端架構的演進

你想成爲一名架構師,對嗎?別對我撒謊,我知道你想成爲架構師。即使你不想,你還是想成爲一名更好的開發者。否則,你就不會花時間閱讀這篇文章😁

這種態度值得讚賞。畢竟,我們都希望在自己所從事的領域變得更好,即使不能稱爲最好。我在這裏就是爲了幫助你實現這一目標。

那麼,你如何成爲一名架構師呢?當然是通過學習所有的架構!顯然這不現實。你不需要知道所有的架構。你也不需要對所有的架構都有經驗。但是,至少了解最流行的幾種架構,比如 N-Layered、DDD、Hexagon、Onion 和 Clean 架構;瞭解它們的歷史、用途以及它們之間的區別,無疑會讓你在與其他開發者的比較中脫穎而出。

希望你感興趣,讓我們開始吧。

一切始於何處?

回到那些美好的過去,根本沒有架構的概念。那是多麼幸福的日子啊,你只需要瞭解 GoF 設計模式,就能自稱爲架構師。

然而,隨着計算機變得更加強大,用戶的需求也增加,導致應用程序的複雜性不斷增加。

開發人員首先解決的問題是將用戶界面與業務邏輯分離。 根據不同的用戶界面框架,出現了各種類似 MVC 的模式:

這雖然有效,但效果不是很好。如果你和我一樣來自 C# 社區,你可能錯誤地認爲那些圖表上稱爲 “Model” 的黃色方框只是 DTO(數據傳輸對象)。這完全是因爲微軟的錯。他們用 ASP MVC 框架把我們搞糊塗了。可惡的微軟!

實際上,在這裏,“Model” 代表的是領域模型,也就是業務邏輯,在任何應用程序中都非常關鍵。

你能猜到上述三個組件中哪個引起的問題最多嗎?視圖只是簡單的圖像和按鈕,控制器充當中間人,而所有的複雜性都集中在模型中。

那個時期,GoF 設計模式已經不夠用。因此,新的想法必須出現。我們如何處理複雜性呢?沒錯!分而治之。 我們已經在 MVC 中這樣做過了,所以讓我們再次這樣做。

2002 年:N-Layered(N 層架構)

理想的架構並非一蹴而就。就像所有事物一樣,它是通過嘗試和錯誤發展而來的。

那位開創軟件開發架構並對接下來的幾代開發者產生影響的人叫 Martin Fowler。他的觀點是:

於是他們開始行動。

他發表了《企業應用架構模式》一書,其中描述了 N 層架構。

這個想法很簡單,就是將所有相關的代碼分組並將其稱爲不同的層

但是,還有更多的事情要做。Fowler 知道不一致的危害有多大。因此,爲了避免我們自己給自己惹麻煩,他試圖給出一些限制和指導:

這些規則不僅幫助開發人員擺脫了代碼重複,而且最終能幫助他們構建代碼。

儘管這些規則相當靈活,但在實踐中,對於大多數項目來說,3 個層已經足夠了。

在這裏,我們明確將業務邏輯與用戶界面分離開來。數據庫的重要性與業務規則相當,因此它有自己的層次。實際上,所有外部技術也可以放在這最後一層。一切都按照書中所說的進行。

如果你對那些彩色矩形和箭頭的意義感到困惑,不用擔心,很簡單。這些層只是解決方案中的項目,箭頭表示它們之間的依賴關係。

分離並不一定要通過項目進行物理上的分離,而可以通過文件夾進行邏輯上的分離。你也可以將兩種方法結合起來,使用最適合你的方式。

文件夾和項目之間的區別很大。 實際上,項目能夠幫助你更好地控制依賴關係。而使用文件夾時,你可能甚至不會意識到某個層開始使用另一個層的組件。另一方面,如果項目過多,代碼會變得更加脆弱和難以維護。

請記住,這並沒有嚴格的規定。你可以根據實際情況選擇適合你的方式。這總是一個可靠性和複雜性之間的權衡。我在這裏的建議是,除非確實需要,不要創建過多的項目,一個項目對應一個層已經足夠了。

每個層通過其 API 調用下一層,通常以接口的形式表示。每個類的訪問修飾符和這些層同樣重要:

現在這對你來說可能顯而易見,但那只是因爲你沒有經歷過真正困難的時刻。使用總是容易的,發明卻很難。

2003 年:DDD(領域驅動設計)

在 2003 年,來自波士頓的一位年輕開發者 Eric Evans 發表了他自己的書《領域驅動設計:軟件核心複雜性應對之道》,這本書至少讓 Martin 感到非常傷心。

實際上,DDD 是一個獨立的主題,需要在自己的系列文章中詳細描述,所以我們現在不會展開介紹,只關注它所引入的所有架構變化。

Evans 贊同 Fowler 的所有觀點,即項目的依賴關係應該是單向的。然而,他也提到低層模塊可以調用上層模塊,前提是不違反依賴關係的方向規則。這可以通過回調、觀察者模式等方式實現。

他還注意到控制器具有過多的邏輯,於是將其移至另一個稱爲應用層的層級中。我們開始看到用例的雛形,但尚未完全發展起來。

然而,Evans 所做的最重要的事情是說 “忽略數據庫,業務邏輯更重要”。他說了這句話,然後卻沒有采取實質性的行動。是的,是的,我知道……DDD 等等。然而,從架構的角度來看,他並沒有做出太多改變。

在他的架構中,定義了以下層級:

你可以看到,他進行了一些重命名。

用戶界面(User Interface)意味着你有用戶,但並不總是這樣。有時它是針對用戶的圖形用戶界面(GUI),有時是針對開發人員的命令行界面(CLI),而通常它是針對程序的應用程序編程接口(API)。表示層(Presentation Layer)只是一個更通用和合適的名稱。

業務邏輯(Business logic)對一些開發人員來說很令人困惑,尤其是那些根本沒有做業務的開發人員,因此引入了一個新名稱 —— 領域(Domain)。

數據庫並不是我們使用的唯一外部工具,所以所有的電子郵件發送器、事件總線、SQL 和其他瑣碎的東西都被移動到了基礎設施層。

基本上就是這樣。在這裏進行了一些重命名,再加上新增了一個層級。我們在該領域付出了很多努力。但這仍然是相同的架構,具有相同的依賴關係。要是他當時知道依賴反轉原則就好了。

2005 年:六邊形架構(Ports and Adapters)

以前,模塊必須引用行中的下一個模塊。隨着依賴反轉原則的發現,一切都改變了。

這對於軟件開發人員來說是一個難得的機會。我們終於學會了如何控制依賴關係的方向,將其指向我們希望的方式!這意味着業務邏輯不再引用數據訪問層。如果你想知道爲什麼這是可能的,以及接口與此有何關係,你可以在這裏找到答案:

https://medium.com/@iamprovidence/from-3-layered-to-ddd-architecture-in-one-step-f3de204bec2e

第一個看到這個潛力的人是 Alistair Cockburn。這傢伙吸很嗨,畫了一個六邊形,試圖召喚撒旦,等等。我不需要告訴你,你自己更瞭解在搖滾派對上發生的情況。沒什麼特別的,有一天你喝了很多酒,第二天早上醒來時帶着嚴重的宿醉,你意外地發現了一種新的架構。

Alistair 對矩形感到厭倦,於是他畫了一個六邊形,爲每個東西想出了兩個名字,試圖讓它們變得神聖起來。但不要被嚇到,我的開發夥伴。實際上,這種架構並不比 N 層架構複雜:

這其中有很多旋轉和移動,讓我們看看實際發生了什麼。

Cockburn 實現了 Evans 的夢想。現在,Domain 是系統的核心組件,不僅在言辭上,而且在行動上也是。它不再引用其他項目。

爲了強調它真正是核心,業務邏輯被重命名爲核心(Core)。

基礎設施模塊被分成兩部分 —— 抽象(接口)和實現。抽象成爲業務邏輯的一部分,並被重命名爲端口(ports)。實現部分保留在基礎設施層中,現在它們被稱爲適配器(adapters)。實際上,UI 和數據庫(DB)位於相同的框架層,因此它們經歷了相同的命運。

將基礎設施的接口放在業務邏輯中,使 Domain 變得自治且無依賴。結果,業務邏輯可以在任何環境中使用任何工具。想要更改數據庫?只需更改實現部分,實現所需的適配器,並將其 “插入” 到可用的端口中。

任何適配器的更改(數據庫、電子郵件發送器、UI)都不會影響業務邏輯。接口保持不變。

每個組件都可以單獨部署。如果更改數據訪問,只需重新構建數據訪問部分。如果只更改 UI,只需更改 UI 部分。

由於可以單獨部署模塊,這意味着它們可以單獨開發。

只有優點。

WbgDaH

我忘了提到,調用我們系統的適配器稱爲主要適配器(驅動)。被我們系統調用的適配器稱爲次要適配器(被驅動)。雖然這不重要,但瞭解這一點會讓你聽起來很博學。

就解決方案結構而言,以下對我來說效果最好:

VytpyN

再次強調,文件夾與項目是你自己決定的事情。

只需按照引用關係,並確保它們不會跨越不應跨越的地方:

CnCFpN

2008 年:洋蔥架構

這個故事有點令人毛骨悚然,所以做好準備吧。

Jeffrey Palermo。這是一個充滿悲傷和黑暗的故事,講述了一個男孩童年時被洋蔥的殘忍思考所困擾的悲傷故事。隨着他的成長,他心中燃燒着一種憤恨,懷着復仇的承諾。

nKV11e

而相信我,他對這個承諾始終如一。這個小洋蔥讓全世界數百萬開發人員哭泣,向他們的母親尋求安慰。

這種架構從端口和適配器中得到了很多提升。它仍然涉及依賴反轉。它按照抽象和實現分割代碼。端口仍然是業務邏輯的一部分。只不過這次,Palermo 從 Evans 的模式中添加了應用層,該層也可以包含一些端口。

這種架構面臨的最大挑戰,也是導致混淆的原因,是模塊之間的依賴關係。

然而,規則很簡單:任何外層只能且只能依賴於內層

不夠簡單,對吧?我也是這麼想的。那麼,讓我們來剖析一下這個洋蔥。

Domain 位於中心。它內部沒有內層,因此不應依賴於任何其他層。

應用層僅包裹領域,所以它只應該依賴於 Domain。

基礎設施層和展示層位於同一級別,它們不能相互依賴,但可以依賴於應用層和 Domain,因爲所有所需接口都在其中定義。

你還可以看到它擁有 DDD 架構中的所有模塊,但以不同的方式處理它們。這實際上非常重要!關鍵在於將很少發生修改的組件放在中間,並將頻繁發生修改的組件放在邊緣。

應用層或任何其他層的更改不會影響領域,只會影響相關的層。 只有當業務邏輯發生變化時,Domain 纔會發生變化,而這種情況無論如何都會影響整個系統。

這是理論上的情況。在實踐中,你的組合根(Main() 函數,在其中註冊所有依賴項並將模塊組合在一起)將成爲展示層(ASP、WPF、CLI)的一部分,因此圖表將如下所示:

對你來說這個看起來熟悉嗎?這就是 N 層架構,只是組件的順序不同。

不管它的外觀如何,無論是六邊形、端口還是洋蔥,你的最終目標是將依賴關係以無環圖或樹形結構的形式呈現出來。

2012 年:清潔架構

有個名叫 Bob 的人, 他是最優雅的程序員, 他的敏捷之舞和完美架構, 讓你的代碼嶄新光亮。

我是說,要講述關於架構的文章,就不能不提到 Robert C.Martin。

他看到了關於架構的熱潮,並決定加入其中。Martin 瞭解開發者的主要祕密,因此他毫不掩飾地借用別人的想法,並將其稱爲自己的。

開個玩笑,如今很少有原創的想法,大家都在相互借鑑。讓我們看看 Martin 在這裏帶來了什麼:

我們可憐的 Domain 再次改名,現在稱爲實體(Entities)。但那不僅僅是改名而已。它意味着你不會再有領域服務和貧血模型,而是擁有數據和行爲的豐富類。

倉儲接口和其他端口從領域層移到應用層。而應用層也得到了一個更合適的名稱 —— 用例(Use Cases)。

展示層和基礎設施層保持不變。然而,Martin 還在頂部添加了一個額外的層,其中包括框架、DLL 和其他外部依賴。這並不意味着你的數據庫將引用實體,它只是防止內部層引用外部工具。

再次強調,沒有嚴格的規定。你可以在任何級別添加任意多的層。所以如果你想爲領域服務定義一個層,你可以這樣做。

Martin 還在架構旁邊畫了一個小圖。

圖中顯示用戶通過觸發控制器的端點與系統進行交互,控制器調用一個用例,然後通過展示器返回數據(黑線)。用例可以通過接口調用任何它所需的端口(綠線)。而實際的實現則位於外層(橙線)。

圖表試圖強調執行流程(虛線)並不總是與依賴關係方向(實線)相對應,這就是依賴倒置原則。

基本上,它再次強調了控制反轉的使用。在我們討論端口和適配器時,你已經見過這一點。

通常在 ASP 中,我們沒有單獨的展示器組件。這也由控制器來完成。因此,整個圖表可以在代碼中表示爲:

class OrderController : ControllerBase, IInputPort, IOutputPort
{
    [HttpGet]
    public IActionResult Get(int id)
    {
        _getOrderUserCase.Execute(id);

        return DisplayOutputPortResult();
    }
}

其他形式的隔離

所有這些架構都旨在通過分離責任來將一個代碼從另一個代碼中隔離出來。然而,還有其他形式的隔離:垂直切片、有界上下文、模塊、微服務等。這些方法的目標是根據功能來劃分代碼。

有些人不認爲它們是 “真正的” 架構方法,而有些人則認爲它們是。這取決於你的觀點。最終,它們可能會發展到一種程度,在那個程度上它們可能會使用上述任何一種架構風格,甚至是它們的組合:

結論

在本文中,我們討論了 N-layered、DDD、六邊形、洋蔥和清潔架構。這些只是衆多存在的架構中的一部分,是一些比較出名的架構。你可能還聽說過 BCE、DCI 等。

儘管在細節上可能存在一些差異,但所有這些架構實際上是非常相似的。它們都有着相同的目標 —— 分離責任。它們通過將代碼分割成不同的層來實現這一目標。唯一的區別在於定義了哪些組件以及這些層之間存在什麼樣的依賴關係。

現在你對整個情況有了全面的瞭解,我強烈鼓勵你再次閱讀本文。自己明白不同架構之間的差異。你還可以嘗試自己動手進行項目實踐。編寫一些帶有接口的類,關注項目之間的引用關係,接口和實現的放置位置,以及所使用的訪問修飾符。

希望從現在開始,每當你創建一個類、審查一個 Pull Request,或者與你的同事進行討論時,你都能有意識地思考並質疑這些事情。

作者:張偉

來源:分佈式實驗室

原文:https://medium.com/@iamprovidence/backend-side-architecture-evolution-n-layered-ddd-hexagon-onion-clean-architecture-643d72444ce4

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/sGaPduMftg8s9ReHYm_kuQ