用六邊形架構構建可維護系統 - IlkkaSeppälä
傳統分層架構存在的謬誤:這篇部落格文章是關於實施Alistair Cockburn的 六角形 架構。我們先來談談分層架構。這是一種眾所周知的架構模式,它將應用程式組織到每個都有其特定用途的層中。資料庫層負責資料事務,業務層負責業務邏輯,表示層處理使用者輸入。分層架構實現了所謂的關注分離原則,這導致了更多可維護的應用程式。對軟體中某個區域的更改不會影響其他區域。
傳統分層:
表現層 -- > 業務層 --->資料層
這種構建應用程式的方式可以被認為是簡單而有效的。但它也有幾個缺點。當您看到使用分層架構實現的應用程式時,應用程式核心在哪裡?是資料庫嗎?也許應該是業務邏輯,一些小東西分散到表現層。這是分層的典型問題:沒有應用程式核心!只有層和核心邏輯分散在這裡或那裡。當業務邏輯開始洩漏到表現層時,無法在沒有使用者介面的情況下測試應用程式。
一個核心,多個埠和介面卡
Hexagonal Architecture通過圍繞一個核心來構建應用程式來解決這個問題。主要目標是建立完全可測試的系統,這些系統可以由使用者,程式和批處理指令碼在資料庫中獨立驅動。
僅核心並不是很有用。必須要驅動這個應用程式,呼叫業務邏輯方法。它可以是HTTP請求,自動測試或整合API。這些用於驅動我們稱為主埠的應用程式的介面和使用它們的模組是主介面卡。
此外,核心有其依賴性。例如,可能存在核心要求檢索和更新資料的資料儲存模組。由核心驅動的這些模組的介面稱為應用程式的輔助埠。
輔助埠可以具有一個或多個實現。例如,可能存在用於測試的模擬資料庫和用於執行應用程式的真實資料庫。輔助埠實現稱為輔助介面卡。這裡有別名為Ports and Adapters for Hexagonal Architecture。描述相同概念的其他建築模式是Uncle Bob的 Clean Architecture 和Jeffrey Palermo的 Onion Architecture 。
六邊形架構:
該圖顯示了領域這個核心如何被六邊形多邊上的埠包圍。實際的埠數量不必是六個,它可以更少,也可以更多,具體取決於應用程式的需求。在外六邊形層上駐留主要和次要介面卡。
“裸體物件Naked Objects ”設計模式被認為是六邊形體系結構的實現。 Apache Isis 框架使用裸體物件,使用者定義領域物件,框架自動生成使用者介面和REST API。
彩票系統
接下來,我們將通過構建彩票系統來演示六邊形架構。彩票系統將提供兩個主要埠:一個用於使用者提交彩票,另一個用於系統管理員執行抽獎。 原始碼Github
輔助埠包括彩票資料庫,用於電匯的銀行業務以及用於處理和儲存彩票事件的事件日誌。系統的六邊形可以在下圖中看到。
從核心概念入手:
我們從系統核心開始實現。首先,我們需要確定彩票系統的核心概念。可能最重要的一個核心是彩票。在“彩票”這個模型中,您應該選擇號碼並提供您的聯絡方式。這導致我們編寫以下類。
LotteryTicket類包含LotteryNumbers和PlayerDetails。
LotteryNumbers類包含儲存指定數字或生成隨機數的方法,並測試數字是否與另一個LotteryNumbers例項相等。 PlayerDetails 是一個簡單的價值物件,包含玩家的電子郵件地址,銀行帳號和電話號碼。
核心業務邏輯:
現在我們有了代表我們核心概念的名詞,我們需要實現定義系統工作方式的核心業務邏輯。在類LotteryAdministration和LotteryService類中,我們編寫了彩票播放器和系統管理員所需的方法。
管理員LotteryAdministration類可以使用resetLottery()方法開始新的彩票輪次。在這個階段,玩家將他們的彩票提交到資料庫中,並且當時間到期時,管理部門要求performLottery()繪製中獎號碼並檢查每張獎金的門票。
彩票玩家用於submitTicket()提交彩票輪票。抽籤結束後checkTicketForPrize()告訴球員他們是否贏了。
LotteryAdministration並且LotteryService依賴於彩票資料庫,銀行和事件日誌埠。我們使用 Guice 依賴注入框架為每個目的提供正確的實現類。核心邏輯在 LotteryTest中 進行測試。
玩家的主要介面卡:
現在核心實現準備就緒,我們需要為玩家定義主介面卡。我們引入ConsoleLottery類來提供允許玩家與彩票系統互動的命令列介面。
它有命令檢視和轉移銀行帳戶資金,提交和檢查彩票。
管理員的主介面卡:
我們還需要定義面向介面卡的彩票管理員。這是另一個命名的命令列介面ConsoleAdministration。
介面的命令允許我們檢視提交的票證,執行抽獎抽獎和重置彩票資料庫。
銀行的二級埠:
接下來,我們實現輔助埠和介面卡。第一個是銀行支援,使我們能夠操縱銀行賬戶資金。為了解釋這個概念,玩家可以在彩票上寫下他的銀行賬號,如果彩票贏了彩票,系統會自動匯款。
銀行埠有兩個用於不同目的的介面卡。
第一個InMemoryBank是基於簡單HashMap的測試實現。彩票服務的銀行賬戶被靜態初始化,以包含足夠的資金來支付獎金,以防一些彩票獲勝。
另一個介面卡MongoBank基於Mongo,適合生產使用。執行任一命令列介面都使用此介面卡。
事件日誌的輔助埠:
另一個輔助埠是彩票事件日誌。當玩家提交彩票並進行抽獎時傳送事件。
我們有兩個適用於此埠的介面卡:第一個StdOutEventLog用於測試,只是將事件傳送到標準輸出。第二種MongoEventLog是更復雜,具有持久儲存並且基於Mongo。
資料庫的輔助埠:
最後一個輔助埠是資料庫。它包含儲存和檢索彩票的方法。
該埠有兩個介面卡。這LotteryTicketInMemoryRepository是一個模擬資料庫,僅將其內容儲存在記憶體中,用於測試。該MongoTicketRepository用於生產執行和在應用程式重新啟動提供持久儲存。
彩票應用:
有了所有部分,我們建立了一個命令列應用程式來驅動彩票系統。測試應用程式使用管理方法開始抽獎輪次並開始從玩家收集彩票。一旦提交了所有彩票,就執行彩票抽獎並檢查所有提交的彩票以獲得勝利。
執行測試應用程式會生成以下輸出:
Lottery ticket for [email protected] was submitted. Bank account 265-748 was charged for 3 credits. Lottery ticket for [email protected] was submitted. Bank account 024-653 was charged for 3 credits. Lottery ticket for [email protected] was submitted. Bank account 842-404 was charged for 3 credits. Lottery ticket for [email protected] was submitted. Bank account 663-765 was charged for 3 credits.
最後的話
使用六邊形體系結構實現的應用程式是一種維護和使用的樂趣。框架,使用者介面和資料庫等實現細節被推出核心,應用程式可以在沒有它們的情況下工作。我們可以清楚地指出六邊形的中心,並說這是我們的應用程式,它使用這些技術來實現子模組介面。限制僅通過埠發生的通訊會強制應用程式生成可測試和可維護的程式碼。
Hexagonal Architecture的完整演示應用程式可在 Java Design Patterns Github儲存庫中找到。