共用方式為


架構原則

小提示

此內容摘自電子書《使用 ASP.NET Core 和 Azure 架構現代化 Web 應用程式》,可在 .NET Docs 上取得,或下載免費 PDF 以便離線閱讀。

ASP.NET Core 與 Azure 架構現代 Web 應用程式的電子書封面縮圖。

“如果建築商以程序設計人員撰寫程式的方式建造建築,那麼第一個來的木鳥將摧毀文明。
- 傑拉爾德·溫伯格

您應該在設計和規劃軟體解決方案時,著重於可維護性。 本節中概述的原則可協助您進行架構決策,以產生全新且可維護的應用程式。 一般而言,這些原則會引導您構建出由離散元件組成的應用程式,這些元件不會與應用程式的其他部分緊密結合,而是透過明確的介面或訊息系統進行通訊。

常見的設計原則

關注點分離

開發時的指導原則是 分離考慮。 此原則判斷提示軟體應該根據其執行的工作類型來分隔。 例如,請考慮應用程式,其中包含用來識別要向用戶顯示的值得注意項目的邏輯,以及以特定方式格式化這類專案,使其更明顯。 負責選擇要格式化項目的行為應該與負責格式化項目的行為分開,因為這些行為是分開的職責,只是彼此偶然相關。

在架構上,您可以透過邏輯方式建置應用程式,以遵循此原則,將核心商務行為與基礎結構和使用者介面邏輯分開。 在理想情況下,商務規則和邏輯應該位於不同的專案中,這不應相依於應用程式中的其他專案。 這種分離有助於確保商業模式易於測試,而且可以在不緊密結合到低階實作細節的情況下演變(如果基礎設施的考量取決於商業層中所定義的抽象概念,這也很有幫助)。 關注點分離是應用程式架構中使用層次的關鍵考量。

封裝

應用程式的不同部分應該使用 封裝 來隔離它們與應用程式的其他部分。 只要不違反外部合約,應用程式元件和層級就應該能夠調整其內部實作,而不會中斷共同作業者。 適當地使用封裝有助於在應用程式設計中實現鬆散結合和模組化,因為只要維護相同的介面,就可以將物件和套件取代為替代實作。

在類別中,封裝是藉由限制對類別內部狀態的外部存取來達成。 如果外部行為者想要操控物件的狀態,它應該透過定義完善的函式(或屬性 setter),而不是直接存取物件的私有狀態。 同樣地,應用程式元件和應用程式本身應該公開妥善定義的介面,供共同作業者使用,而不是允許直接修改其狀態。 只要維持公開合約,此方法就能讓應用程式內部設計在不影響合作的情況下隨著時間自由演進。

可變動的全域狀態與封裝原則相悖。 從某個函式中可變動全域狀態擷取的值,無法依賴在另一個函式中具有相同的值(甚至在同一個函式中更進一步)。 瞭解可變動全域狀態的擔憂是 C# 等程式設計語言支援不同範圍規則的原因之一,這些規則會隨處使用,從語句到方法到類別。 值得注意的是,依賴中央資料庫來整合應用程式內部及外部環節的以數據為導向的架構,選擇依賴由資料庫所代表的可變動的全域狀態。 網域驅動設計和清晰架構中的主要考慮是如何封裝數據存取,以及如何確保應用程式狀態不會因直接存取其持久性格式而失效。

依賴反轉原則

應用程式內的相依性方向應為抽象方向,而不是實作詳細數據。 大部分的應用程式都是如此編寫,讓編譯時的依賴性流向運行時的執行,產生直接依賴圖。 也就是說,如果類別 A 呼叫類別 B 的方法,而類別 B 會呼叫類別 C 的方法,則編譯時間類別 A 將相依於類別 B,而類別 B 將相依於類別 C,如圖 4-1 所示。

直接相依性圖表

圖 4-1。 直接相依性圖形。

套用相依性反轉原則可讓 A 在 B 實作的抽象概念上呼叫方法,讓 A 能夠在運行時間呼叫 B,但讓 B 相依於編譯時期由 A 控制的介面(因此, 反轉 一般的編譯時間相依性)。 在運行時間,程序執行流程保持不變,但介面的引進表示這些介面的不同實作很容易插入。

反向相依性圖表

圖 4-2。 反向相依性圖表。

相依性反轉是構建鬆散耦合應用程式的關鍵部分,因為實作細節可以撰寫成依賴並實作較高層級的抽象概念,而不是相反的方式。 因此,產生的應用程式更容易測試、模組化且可維護。 藉由遵循相依性反轉原則,就可以實作相 依性插入

明確相依性

方法和類別應該明確地要求他們需要的任何共同作業物件,才能正確運作。 它稱為 「明確相依性原則」。 類別建構函式提供機會讓類別識別所需的專案,以便處於有效狀態並正常運作。 如果您定義可建構和呼叫的類別,但只有在某些全域或基礎結構元件已就緒時,才會正常運作,這些類別會與其客戶端 不誠實 。 建構子合約告訴客戶端,它只需要指定的項目(如果類別僅使用無參數建構子,可能什麼都不需要),但是到了執行時卻發現該物件確實還需要其他東西。

遵循明確的相依性原則,您的類別和方法會坦率地告知用戶端其運作所需的資源。 遵循原則,讓您的程式代碼更具自我記載,而且程式代碼合約更方便使用者,因為只要使用者提供方法或建構函式參數形式所需的內容,他們正在使用的物件就會在運行時間正確運作。

單一責任

單一責任原則適用於面向對象設計,但也可以視為類似考慮區隔的架構原則。 它指出對象應該只有一個責任,而且應該只有一個變更的理由。 具體來說,對象應該變更的唯一情況是,其執行其一項責任的方式必須更新。 遵循此原則有助於產生更鬆散結合和模組化的系統,因為許多新行為可以實作為新類別,而不是將額外的責任新增至現有的類別。 新增類別一律比變更現有的類別更安全,因為程式代碼尚未相依於新的類別。

在整合型應用程式中,我們可以將單一責任原則套用至應用程式中的層級。 表示責任應該保留在UI專案中,而數據存取責任應該保留在基礎結構專案中。 商業邏輯應保留在應用程式核心專案中,這樣不僅可以輕鬆測試,還能獨立於其他職責自由發展。

當此原則套用至應用程式架構並採用至其邏輯端點時,您會取得微服務。 指定的微服務應該具有單一責任。 如果您需要擴充系統的行為,通常最好藉由新增其他微服務來執行此作業,而不是將責任新增至現有的微服務。

深入瞭解微服務架構

不重複原則 (DRY)

應用程式應該避免在多個位置指定與特定概念相關的行為,因為這種做法是錯誤的常見來源。 在某些時候,需求變更需要變更此行為。 至少有一個行為實例無法更新,而且系統的行為可能會不一致。

與其複製邏輯,不如將其封裝在程式設計建構中。 讓此建構成為此行為的唯一控制單位,並讓應用程式的任何其他部分需要使用此新的構建。

備註

避免將只巧合重複的行為系結在一起。 例如,僅僅因為兩個不同的常數都有相同的值,這並不表示您應該只有一個常數,從概念上來說,它們指的是不同的事物。 相較於結合錯誤的抽象概念,重複總是更可取。

持續性無知

持續性無知 (PI) 是指需要保存的類型,但其程式代碼不受持續性技術選擇影響。 .NET 中的這類類型有時稱為「一般舊 CLR 物件」(POCO),因為它們不需要繼承自特定基類或實作特定介面。 持續性無知是有價值的,因為它允許以多種方式保存相同的商務模型,為應用程式提供額外的彈性。 持續性選擇可能會隨著時間而改變,例如從一種資料庫技術轉換為另一種技術。除了應用程式原本使用的技術,可能還需要其他形式的資料持久性,例如加入 Redis 快取或 Azure Cosmos DB,除了使用關係資料庫。

違反此原則的一些範例包括:

  • 必要的底層類別。

  • 必要的介面實作。

  • 負責自行儲存自身狀態的類別(例如活動記錄模式)。

  • 必要的無參數建構函式。

  • 需要虛擬關鍵詞的屬性。

  • 與持久性相關的必要屬性。

類別具有上述任何特性或行為的需求,會增加要保存的類型與持續性技術選擇之間的結合,使得未來更難以採用新的數據存取策略。

限界上下文

限定內容 是設計 Domain-Driven 中的核心模式。 它們藉由將大型應用程式或組織分解成個別的概念模組,提供解決複雜度的方法。 然後,每個概念模組都代表與其他語境分開的語境(因此界限分明),而且可以獨立演變。 在理想情況下,每個限定內容都應該可以自由地為它內的概念選擇自己的名稱,而且應該具有專屬的持續性存放區存取權。

個別 Web 應用程式至少應努力成為自己的界限上下文,擁有自己的商業模型的持久性儲存庫,而非與其他應用程式共享資料庫。 界限內容之間的通訊會透過程序設計介面進行,而不是透過共用資料庫進行,這可讓商業規則和事件在回應所發生的變更時發生。 界限上下文會密切對應至微服務,理想情況下,微服務也應作為獨立的界限上下文來實作。

其他資源