分層(Layering)是軟體設計者用來拆解複雜系統最常見的技術之一。無論是機器架構(從程式語言到 CPU 指令集再到邏輯閘)還是網路協定(FTP → TCP → IP → Ethernet),都能看到分層的身影。
分層的核心概念#
當我們以層次的角度思考系統時,可以將軟體中的主要子系統想像成一層層的蛋糕:
- 上層使用下層定義的服務
- 下層不知道上層的存在
- 每一層對上層隱藏其下方所有層次的細節
大多數分層架構是不透明的(opaque)——第 4 層使用第 3 層的服務,第 3 層使用第 2 層,但第 4 層不能直接使用第 2 層。不過並非所有架構都如此嚴格。
分層的優點#
- 獨立理解:可以單獨理解一個層,而不需要了解其他層的細節
- 可替換性:可以用提供相同基本服務的替代實作來替換某一層
- 最小化依賴:層與層之間的依賴被降到最低
- 標準化基礎:層是制定標準的好地方(如 TCP/IP)
- 服務複用:一旦某層建立完成,許多高層級服務都可以使用它
分層的缺點#
- 級聯變更:某些變更會需要逐層修改(例如在 UI 新增一個欄位,資料庫也得加上,中間每層都得配合)
- 效能損耗:每一層通常需要進行資料格式的轉換,但封裝底層功能帶來的效率提升往往足以彌補
分層架構中最困難的部分是決定要分幾層、每一層的職責是什麼。
企業應用分層的演進#
早期:批次處理時代#
在早期批次系統中,程式直接操作檔案(ISAM、VSAM 等),沒有明確的分層概念。
1990 年代:Client-Server 的興起#
Client-Server 系統將應用分為兩層:
- Client 端:持有 UI 和應用程式碼(VB、PowerBuilder、Delphi)
- Server 端:通常是關聯式資料庫
這種架構在「顯示與簡單更新關聯式資料」的場景下運作良好。但當業務邏輯(商業規則、驗證、計算等)變得複雜時,問題就浮現了:
- 邏輯嵌在 UI 畫面中,難以維護與重用
- 將邏輯放入預存程序(Stored Procedures)又受限於結構化機制不足
物件導向的解答:三層架構#
物件導向社群提出了解決方案——三層架構:
- Presentation Layer:UI 層
- Domain Layer:業務邏輯層
- Data Source:資料來源層
Web 時代的衝擊#
Web 的興起改變了遊戲規則。如果所有業務邏輯都埋在 Rich Client 中,那麼要支援 Web 就得全部重寫。而設計良好的三層系統只需新增一個 Presentation 層即可。
Layer(層)與 Tier(階)是不同的概念。 Layer 是邏輯上的分離,Tier 是物理上的分離。用本機資料庫跑三層應用,物理上只有一台機器,但邏輯上仍然是三個不同的層。
三個主要層次#
| 層次 | 職責 |
|---|---|
| Presentation | 提供服務、顯示資訊、處理使用者請求(滑鼠點擊、HTTP 請求、命令列、批次 API) |
| Domain | 系統真正的核心——業務邏輯 |
| Data Source | 與資料庫、訊息系統、交易管理器及其他套件的通訊 |
Domain Layer 的本質#
Domain Logic(又稱 Business Logic)是應用程式為其所服務的領域需要做的工作,包括:
- 根據輸入與儲存資料進行計算
- 驗證來自 Presentation 的資料
- 決定要調度哪些 Data Source 邏輯
Presentation 與 Data Source 的相似性#
Presentation 和 Data Source 都是與外部世界連接的界面。這正是 Alistair Cockburn 的 Hexagonal Architecture 背後的邏輯——將系統視為被外部介面包圍的核心。
如何判斷邏輯是否洩漏到了 Presentation 層? 想像要為應用程式加上一個完全不同的介面(例如命令列介面)。如果需要複製任何功能,那就是 Domain Logic 洩漏到了 Presentation 的徵兆。
分層的實務建議#
- 即使簡單到一個腳本就能完成的應用,也應該至少在 subroutine 層級做分離
- 隨著系統複雜度增加,逐步將三個層分為不同的 class、不同的 package
- Domain 和 Data Source 絕不應依賴 Presentation——這讓替換不同的 Presentation 變得容易
選擇各層的執行位置#
一般原則#
- Data Source:幾乎總是在 Server 端
- Presentation:取決於 UI 類型——Rich Client 在 Client 端,Web 在 Server 端
- Domain Logic:Server 端是最佳選擇,除非需要離線操作或更高的回應性
為何偏好 Server 端#
Server 端執行的最大優勢是易於升級與修復——不需要部署到每台桌機,也不需要擔心桌面軟體的相容性問題。
避免跨程序分層#
選定執行節點後,應盡量將所有程式碼放在單一程序中。不要將層分成不同的程序,除非有充分理由。跨程序會降低效能、增加複雜度,還需要引入 Remote Facade 和 Data Transfer Object 等額外模式。
複雜度助推器#
以下這些因素被 Jens Coldewey 稱為「複雜度助推器」,每一個都帶來高昂的成本:
- 分散式架構(Distribution)
- 明確的多執行緒處理
- 範式鴻溝(如物件/關聯式映射)
- 跨平台開發
- 極端的效能要求(如超過 100 TPS)