# CH17、CH18 # CH 17 邊界:畫線 ###### tags: `無瑕的程式碼:整潔的軟體設計和架構篇` > 邊界:將軟體元素彼此分開,並限制一方對於其他方的了解。 > 有些邊界很早就被畫出來(畫線) > 早早畫出的線是為了<strong>讓決定可以被延緩到很後期再做出決定,並使這些決定不會汙染核心的業務邏輯。</strong> ==架構師的目標== - 盡量減少建置和維護需求系統的所需人力資源。 - 耗盡人力資源 **=** ==耦合==,特別是耦合導致++過早做了不成熟的決定。++ - 何謂不成熟的決定? - 與系統的業務需求(使用案例)無關的決定。比如框架、Database、Web server、工具程式庫、依賴注入等決定。 - 好的系統架構允許盡可能晚的時刻才做出這些決定,而且不會有顯著的影響。 ## 兩個悲慘的故事 ### P公司 - 過早決定採用龐大的三層架構(GUI Server、Middelware(中間件)Server、Database Server) - 實作簡單的功能:每個可執行的檔案,都要包含3個更新的業務物件、四個新的訊息、八個新的處理程序 ### W公司 - 圍繞服務建構的軟體系統,本質上並沒有任何錯誤。 - 錯誤在於**過早**採用和強制使用了遵循[SoA(服務導向架構)](https://zh.wikipedia.org/zh-tw/%E9%9D%A2%E5%90%91%E6%9C%8D%E5%8A%A1%E7%9A%84%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84),也就是大量的領域物件服務。 - 錯誤的代價將浪費大量的工時。 ## FitNesse 1. 編寫自己的Web Server → 推遲對於Web框架選擇的任何決定,讓它能更晚一些。 2. 避免考慮到資料庫的問題,採用與決定無關的設計 → 在所有資料存取和資料儲存體之間,簡單地建立一個介面。 - 開發早期,畫出了業務規則和資料庫之間的**邊界線(Boundary Line)** - 這個決定延緩了對於資料庫的選擇和實作,允許嘗試別的選項(檔案系統選項),但也不阻擋朝原來的方向(使用MySQL)進行。 ## 該畫甚麼線、甚麼時候畫它們? - [x] GUI和業務規則沒關係,畫 - [x] 資料庫和GUI沒關係,畫 - [x] 資料庫和業務規則沒關係,畫 - [ ] 資料庫應該是業務規則**間接**可使用的工具,不需要了解資料庫的任何細節,只需要知道有一組函式可用於獲取或保存資料,因此允許把資料庫隱藏在一個介面之後。 ![](https://i.imgur.com/gfMD0TC.jpg) - BusinessRules使用DatabaseInterface來載入和儲存資料,DatabaseAccess實作介面並指揮實際的Database的操作。 將層次拉高一點來看 (注意箭頭的方線) ![](https://i.imgur.com/dOGxFLU.jpg) - Database知道BusinessRules,BusinessRules不知道Database。 - 這代表DatabaseInterface類別位於BusinessRules元件中,而DatabaseAccess類別位於Database元件中。 > 線的方向很重要,顯示依賴的方向,也就是DB對於BR來說無關緊要,但是沒有BR,DB也無法存在。 ## 輸入跟輸出 > 重要的原則:IO是無關緊要的。 > 雖然我們經常根據IO的行為來思考系統的行為 - Video Game的例子。 ![](https://i.imgur.com/eJsNBf2.jpg) ## Plugin 架構 - 以上模式和==允許第三方Plugin的系統==所使用的模式是一樣的。 > 事實上軟體開發技術的歷史就是<strong>==如何方便地利用plugin來奠定可擴展和可維護的系統架構==</strong>的故事 > 核心業務要與其他元件獨立分別開來 > ![](https://i.imgur.com/aPS9TT0.jpg) ## Plugin 參數 ![](https://i.imgur.com/GBhRpiC.jpg) - 參考依賴結構圖,可以發現這是一個非常不對稱的關係 - 也是我們希望在系統中擁有的關係 (不容易受其他元件影響破壞掉系統) > **以變化為軸**的地方繪製邊界。 > 再次簡單利用++單一職責原則++,SRP告訴我們要在哪裡繪製我們的邊界。 ## 總結 - 在軟體架構中繪製邊界線 1. 將系統化分為以原件組合起來的系統 2. 區分 核心業務規則,與那些與業務規則沒有直接關係的必要plugin 3. 使這些元件(程式碼)指向核心業務 4. 這裡應用了依賴反向原則和穩定抽象原則,依賴箭頭從低層級的細節指向較高層級的抽象。 # CH 18 邊界剖析 ## 跨越邊界 > 定義:==在邊界的另一邊呼叫另一邊的函式並傳遞一些資料。== > 建立適當的跨越邊界技巧,就是在管理原始碼的依賴關係。 ++為什麼是原始碼?++ 因為當一個原始碼模組變動時,其他原始碼模組可能會需要修改或重新編譯、部署。 管理和建置防火牆來抵抗這些變化,就是邊界要做、且只關心的事情。 ## 可怕的單片 架構邊界並沒有嚴格的實際表示法。它只是在單一處理器和位址空間內,按照規範進行的函式和資料分離。 前面將它稱之為原始碼層級的解耦模式(source-level decoupling mode) - 從部屬的角度來看,這只是個可執行檔,故稱之為單片 這些架構幾乎總是依賴某種動態多行來管理內部依賴關係。 這也是近年來OOP成為如此重要的範式的原因之一, 如果沒有OO或多型的某個等孝行是,架構師位了適當的解耦,必須使用函式指標這種危險的作法。(風險很大,因此被迫放棄任何的元件分割方式) ![](https://i.imgur.com/a2houjR.jpg) ## 部署元件 - 架構邊界最簡單的實際表示法是動態鏈結程式庫 (DLL、jar檔等) - 此為部署層級的解耦模式,行為只是將這些單元聚集在一起,比如WAR檔,甚至只是一個目錄。 ## 執行序 - 一種組織排程和執行順序的方式,可能全部包含在一個元件中,也可能跨越遍布在許多元件中。 ## 本地行程 (local process) - 更強大的實體架構邊界。 - 通常透過命令列或等效的系統呼叫來建立。 - 使用socket或其他的作業系統通訊工具來相互通訊。 - 將它看成一個超級元件:由較低層級的元件組成,並透過動態多行來管理之間的依賴關係。 - 原始碼依賴關係指向跨越邊界的相同方向,並始終只向更高層級的元件。 - 較高層級行程的原始碼不可包含較低行程的名稱、實際位址、或註冊表的尋找鍵,架構的目標是要讓較低層級的行程成為更高層級行程的plugin - 代價很高,要特別注意行程之間無謂的通訊。 ## 服務(Service) - 最強的邊界。 - 是一個行程。不依賴於他們的實體位置。 - 與函式呼叫相比,跨服務邊界的通訊非常緩慢,必須小心以避免出現無謂的通訊,且這個層級的通訊必須處理高層級的延遲(latency)。 - 適用於服務的相同規則也適用於本地行程。 ## 總結 大多數的系統都使用超過一個的邊界策略 (除了單片以外) - 服務往往只是表面,內部則是一組相互作用的本地行程 - 服務或本地行程幾乎可以詪定是由原始碼元件組成的單片,或者是一組動態鏈結的部屬元件。 - 這代表系統中的邊界往往混和了 - 本地經常通訊的邊界 - 更關注於延遲的邊界。