---
# System prepended metadata

title: CH17、CH18
tags: [無瑕的程式碼：整潔的軟體設計和架構篇]

---

# 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)。
- 適用於服務的相同規則也適用於本地行程。

## 總結

大多數的系統都使用超過一個的邊界策略 (除了單片以外)

- 服務往往只是表面，內部則是一組相互作用的本地行程
- 服務或本地行程幾乎可以詪定是由原始碼元件組成的單片，或者是一組動態鏈結的部屬元件。
- 這代表系統中的邊界往往混和了
    - 本地經常通訊的邊界
    - 更關注於延遲的邊界。



