架構原則

提示

此內容摘錄自電子書、使用 ASP.NET Core 和 Azure 建構新式 Web 應用程式,可在.NET Docs上取得,或以可離線閱讀的免費可下載 PDF 形式提供。

Architect Modern Web Applications with ASP.NET Core and Azure eBook cover thumbnail.

「如果營造商要像程式設計人員撰寫程式那樣地蓋房子,那麼第一隻經過的啄木鳥將會摧毀文明。」
- Gerald Weinberg

您在架構與設計軟體解決方案時,應該惦記著可維護性。 本節中所述的準則可協助引導您做出將產生可維護之全新應用程式的架構決策。 一般而言,這些原則會引導您利用與應用程式其他部分未緊密結合,而是透過明確介面或傳訊系統進行通訊的不同元件來建置應用程式。

一般設計原則

關注點分離

開發時的一項指導原則是關注點分離。 這個原則判斷提示軟體應該根據它所執行的工作種類來分離。 比方說,假設應用程式中包含邏輯來識別值得注意的項目,以便顯示給使用者,而且特別格式化此類項目,使其更容易被注意到。 負責選擇要格式化哪些專案的行為應該與負責格式化專案的行為分開,因為這些行為是個別的考慮,只與彼此一致。

在架構上,應用程式可以邏輯方式建置,以遵循此原則,方法是將核心商務行為與基礎結構和使用者介面邏輯分開。 在理想情況下,商務規則和邏輯應該位於個別的專案,且不應依賴應用程式中的其他專案。 此區隔有助於確保商務模型很容易進行測試,而且可以在不緊密結合到低階實作詳細資料的情況下演進, (基礎結構考慮是否相依于商務層中所定義的抽象概念) 也有所説明。 關注點分離是在應用程式架構中使用層級背後的一項重要考量。

封裝

應用程式的不同部分應該使用封裝,將它們與應用程式的其他部分隔離。 應用程式元件和層級應該能夠調整內部實作,而且只要不違反外部的合約,便不必中斷其共同作業者。 正確地使用封裝可協助達到應用程式設計中的鬆散結合和模組化,因為物件與套件可以取代為替代的實作,只要維持相同的介面即可。

在類別中,封裝是藉由限制對類別內部狀態的外部存取而達成。 如果外部執行者想要操作物件的狀態,則應透過妥善定義的函式 (或屬性 setter) 來達成,而不是直接存取物件的私用狀態。 同樣地,應用程式元件和應用程式本身應該公開妥善定義的介面,供它們的共同作業者來使用,而不是允許直接修改它們的狀態。 這種方法可釋出應用程式的內部設計來隨著時間演進,而不必擔心這麼做會中斷共同作業者,只要維護公用合約即可。

可變動的全域狀態是封裝的反主題。 從一個函式中可變動全域狀態擷取的值不能依賴在另一個函式中具有相同的值 (或甚至更進一步的相同函式) 。 瞭解可變動全域狀態的考慮是 C# 等程式設計語言支援不同範圍規則的原因之一,這些規則會隨處使用從 語句到類別的方法。 值得注意的是,依賴中央資料庫在應用程式內和之間整合的資料驅動架構本身是,選擇取決於資料庫所代表的可變動全域狀態。 網域驅動設計和全新架構的重要考慮是如何封裝對資料的存取,以及如何確保應用程式狀態不會透過直接存取其持續性格式而失效。

相依性反轉

應用程式內的相依性方向應該是抽象的方向,而不是實作詳細資料。 大部分的應用程式都會寫入,以執行時間執行方向編譯時期相依性流程,產生直接相依性圖形。 也就是說,如果類別 A 呼叫類別 B 的方法,而類別 B 會呼叫類別 C 的方法,則在編譯時期類別 A 將相依于類別 B,而類別 B 將相依于類別 C,如圖 4-1 所示。

Direct dependency graph

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

套用相依性反轉原則可讓 A 在 B 實作的抽象概念上呼叫方法,讓 A 能夠在執行時間呼叫 B,但 B 在編譯時期取決於 A 所控制的介面 (因此 ,反轉 典型的編譯時期相依性) 。 在執行階段,程式執行流程維持不變,但是介面的引進表示可以輕鬆地插入這些介面的不同實作。

Inverted dependency graph

圖 4-2. 反轉相依性圖形。

相依性反轉 是建置鬆散結合應用程式的重要部分,因為實作詳細資料可以撰寫成相依並實作較高層級的抽象概念,而不是另一種方式。 因此,所產生的應用程式會比較可測試、模組化且可維護。 遵循相依性反轉準則,即可達成「相依性插入」

明確相依性

方法和類別應該明確需要正常運作所需的任何共同作業物件。 我將此稱為 明確相依性原則。 類別建構函式會提供一個機會,讓類別能識別它們處於有效狀態並正常運作所需的項目。 如果您定義可建構和呼叫的類別,但只有在特定全域或基礎結構元件已就緒時,這些類別才會正常運作,這些類別 會與其用戶端 一同運作。 建構函式合約告訴用戶端它只需要指定的項目 (如果類別只使用無參數建構函式則可能沒有任何項目),但在執行階段變成物件確實需要其他項目。

藉由遵循明確的相依性原則,您的類別和方法對其用戶端便會誠實告知他們需要要什麼才能運作。 遵循原則可讓程式碼更自我記載,而且您的程式碼合約更容易使用,因為只要使用者提供方法或建構函式參數形式所需的專案,他們所使用的物件就會在執行時間正確運作。

單一責任

單一責任原則適用於物件導向設計,但也可視為類似關注點分離的架構原則。 它指出物件應該只有一項責任,而且應該只有一個變更的原因。 具體而言,物件唯一應該變更的情況是,它執行它的一項責任的方式必須更新時。 遵循此原則有助於產生更鬆散結合和模組化的系統,因為許多新行為可以實作為新類別,而不是將額外的責任新增至現有的類別。 新增類別一比變更現有類別安全,因為還沒有程式碼相依於新的類別。

在整合應用程式中,我們可以在高層級套用單一責任原則至應用程式中的層級。 簡報責任應保留在 UI 專案,而資料存取責任則應保留在基礎結構專案中。 商務邏輯應該保留在應用程式核心專案,在這裡它可以輕鬆地測試,且可以獨立於其他責任之外地持續改進。

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

深入了解微服務架構

不重複原則 (DRY)

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

請不要複製邏輯,而是要將它封裝在程式設計建構中。 讓此建構成為此行為的單一授權,而且讓應用程式中需要這個行為的任何其他部分都使用新建構。

注意

避免將湊巧重複的行為繫結在一起。 例如,只是兩個不同的常數具有相同的值,並不表示您應該只有一個常數,如果在概念上它們是指不同項目的話。 重複一律最好與錯誤的抽象概念結合。

持續性無知

續性無知 (PI) 指的是需要持續的,但其程式碼不會受到持續性技術選項影響的類型。 這類的類型在 .NET 中有時稱為簡單的 CLR 物件 (POCO),因為它們不需要繼承特定的基底類別或實作特定介面。 持續性無知的價值在於它允許以多種方式保存相同的商務模型,為應用程式提供額外的彈性。 除了使用 Redis 快取或 Azure Cosmos DB 之外,持續性選項可能會隨著時間而改變,從某個資料庫技術到另一種,或者除了關係資料庫) 之外,還需要使用任何應用程式以 (啟動的任何持續性形式。

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

  • 必要的基底類別。

  • 必要的介面實作。

  • 負責自行儲存的類別 (例如使用中的記錄模式)。

  • 需要無參數建構函式。

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

  • 持續性特定的必要屬性。

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

繫結內容

繫結內容是 Domain-Driven 設計的中心模式。 它們藉由將大型應用程式或組織的複雜性分成不同的概念模組,提供處理複雜性的方法。 然後,每個概念模組都代表與其他內容分開的內容 (,因此系結) ,而且可以獨立演進。 每個繫結內容在理想情況下應該能自由選擇自己的概念名稱,而且應該對它自己的持續性存放區具有獨佔存取權。

至少,個別 Web 應用程式應該致力於成為自己的繫結內容,並且具有自己商務模型的持續性存放區,而不與其他應用程式共用一個資料庫。 繫結內容之間的通訊會透過程式設計介面進行,而不是透過共用的資料庫,這樣可讓商務邏輯和事件發生以回應發生的變更。 繫結內容與微服務密切對應,而微服務在理想的情況下也實作為自己的個別繫結內容。

其他資源