綜合運用#

前面各章分別探討了系統的不同面向及其處理選項,本章將所有內容彙整起來,回答一個關鍵問題:設計企業應用程式時,應該使用哪些模式?

本章的建議在很大程度上是前面各章建議的重述。作者坦承這些建議具有局限性,並引用 Frodo 在《魔戒》中的話:「不要向精靈求教,因為他們會同時說是與否。」

架構決策並非不可逆轉的。即便你不認同極限程式設計的全部,仍應認真考慮三項技術實踐:持續整合(continuous integration)測試驅動開發(test driven development)重構(refactoring)。這些雖非萬靈丹,但能讓你在需要改變方向時輕鬆許多。


Starting with the Domain Layer#

從領域層開始#

選擇模式的起點是決定領域邏輯的組織方式。三種主要選項為:

  • Transaction Script(交易腳本)
  • Table Module(表格模組)
  • Domain Model(領域模型)

驅動這個選擇的最強因素是領域邏輯的複雜度

Transaction Script#

  • 最簡單的模式,符合多數人熟悉的程序式模型
  • 將每個系統交易的邏輯封裝在一個容易理解的腳本中
  • 容易構建在關聯式資料庫之上
  • 致命缺點:無法良好處理複雜的業務邏輯,特別容易產生重複程式碼
  • 如果只是一個簡單的購物車應用加上基本定價結構,Transaction Script 就能完美勝任
  • 但當邏輯變得複雜時,困難會指數級增長

Domain Model#

  • 位於光譜的另一端,物件導向純粹主義者認為只有這條路
  • 如果應用程式簡單到用 Transaction Script 就能寫,那就不需要 Domain Model
  • 但對於真正複雜的領域邏輯,豐富的 Domain Model 是無可替代的
  • Domain Model 的缺點:學習曲線陡峭(許多人就是無法掌握物件)、與關聯式資料庫的對應關係複雜
  • 一旦習慣了 Domain Model,很少有人願意回到 Transaction Script

Table Module#

  • 介於兩者之間的中間地帶
  • 能處理比 Transaction Script 更複雜的領域邏輯,但無法觸及 Domain Model 的深度
  • .NET 環境中特別強勢,因為大量工具圍繞著 Record Set 建構
  • Table Module 巧妙地利用關聯式資料庫的優勢,同時提供合理的領域邏輯分層

工具的選擇也會影響架構。實務上,你往往需要讓架構配合工具。三種模式中,Table Module 在有配套工具時表現最為突出,對 .NET 環境尤其如此。


Down to the Data Source Layer#

向下到資料來源層#

選定領域層之後,接下來要決定如何連接資料來源。這個決策取決於你的領域層選擇。

搭配 Transaction Script 的資料來源#

  • Transaction Script 通常自己包含資料庫邏輯,但作者建議即便是最簡單的情況也要分離
  • 可選擇 Row Data GatewayTable Data Gateway
  • Row Data Gateway:每筆記錄讀入一個物件,有清楚明確的介面
  • Table Data Gateway:可能需要寫的程式碼較少,但介面較隱含,近似一個美化的 map
  • 關鍵決策在於平台:如果平台提供大量與 Record Set 配合的工具(如 UI 工具或交易式斷線記錄集),則傾向使用 Table Data Gateway
  • 通常不需要其他 O/R 映射模式;也通常不需要 Unit of Work,因為腳本通常對應一個系統交易

搭配 Transaction Script 時,Optimistic Offline Lock 幾乎總是最佳選擇——不僅更容易實作,也符合使用者期望,避免了懸掛的 session 鎖住各種資源。

搭配 Table Module 的資料來源#

  • 選擇 Table Module 的主要原因是有良好的 Record Set 框架
  • 這自然導向 Table Data Gateway,兩者配合天衣無縫
  • 在最佳情況下,Record Set 本身內建了某種並行控制機制,等同於 Unit of Work

搭配 Domain Model 的資料來源#

  • 這裡才是真正有趣的地方——Domain Model 與資料庫的連接最為複雜
  • 簡單的 Domain Model(少數類別、與資料庫結構相近):可使用 Active Record;若想進一步解耦,可選 Table Data Gateway 或 Row Data Gateway
  • 複雜的 Domain Model:需要 Data Mapper,這是讓 Domain Model 盡可能獨立於其他層的做法。但 Data Mapper 也是最複雜的實作模式——除非有簡化映射的手段,否則強烈建議使用映射工具
  • 一旦選用 Data Mapper,大部分 O/R 映射模式都會派上用場,特別推薦 Unit of Work 作為並行控制的焦點

展示層#

  • 展示層的選擇相對獨立於下層
  • 首先決定豐富客戶端(rich client) 還是 HTML 瀏覽器介面——作者偏好 HTML 瀏覽器,除非不得已才選豐富客戶端
  • 無論走哪條路,都推薦 Model View Controller 作為基礎架構
  • Controller 的選擇:Visual Studio 適合 Page Controller + Template View;Java 的 Struts 導向 Front Controller + Template View;文件導向的網站適合 Page Controller,複雜導航適合 Front Controller
  • View 的選擇:取決於團隊使用 server pages 還是 XSLT——Template View 目前佔優勢,但 Transform View 有更好的可測試性。若需統一外觀,考慮 Two Step View

作者偏好將所有東西運行在同一個行程中,以避免緩慢的跨行程呼叫。如果無法做到,應使用 Remote Facade 包裝領域層,並使用 Data Transfer Object 與 Web 伺服器通訊。


Some Technology-Specific Advice#

特定技術的建議#

作者針對 Java/J2EE.NET 兩大平台提供了具體建議。

Java 和 J2EE#

  • 2002 年的大辯論:是否需要 Enterprise Java Beans?答案是不一定——用 POJO(plain old Java objects)和 JDBC 就能做很多事
  • 使用 Transaction Script + Gateway:常見做法是 session beans 搭配 entity beans 做 Row Data Gateway,但如果領域邏輯夠簡單,可以不用 EJB,改用 POJO 搭配 Row Data Gateway 或 Table Data Gateway
  • 使用 Domain Model:當時的正統做法是使用 entity beans。若 Domain Model 簡單,entity beans 配 Active Record 可行;若複雜,則應使用完全獨立於 EJB 的 POJO Domain Model,用 session beans 配 Remote Facades 包裝
  • Entity beans 絕對不要給予遠端介面——它們作為 Domain Model 或 Row Data Gateway 使用,需要細粒度介面

如果使用 entity beans,絕對避免給它們遠端介面。Entity beans 通常作為 Domain Model 或 Row Data Gateway 使用,需要細粒度介面才能運作良好。遠端介面必須永遠是粗粒度的。

.NET#

  • 在微軟世界中,主導模式是 Table Module,搭配強大的工具集(Record Set 為中心)
  • Table Module 是 .NET 平台的預設選擇;作者認為在 .NET 中使用 Transaction Script 幾乎沒有意義
  • .NET 中當然也能建構 Domain Model,但工具不會給予如 Table Module 那樣的額外支持
  • .NET 中不建議使用 Web 服務做內部通訊——沒有必要將 Web 伺服器與領域邏輯分成不同行程,所以 Remote Facade 較少使用

預存程序(Stored Procedures)#

  • 預存程序常是最快的做法,但大多數預存程序環境缺乏良好的結構化機制,且會鎖定特定資料庫廠商
  • 作者傾向避免使用預存程序來處理業務邏輯,除非有明確的效能需求
  • 如需使用,應視為最佳化手段,從領域層中取出方法移入預存程序,而非架構原則
  • 使用預存程序控制資料庫存取(類似 Table Data Gateway)是另一種常見用法

Web 服務#

  • Web 服務的主要價值在於應用程式整合,而非應用程式建構
  • 不應該為了分散而將單一應用拆分成互相對話的 Web 服務
  • 應該先建構你的應用程式,再將各部分以 Web 服務的形式暴露出去,視其為 Remote Facades

不要被 Web 服務的熱潮沖昏頭腦——別忘了分散式物件設計第一定律(第 89 頁)。作者偏好以非同步、訊息導向的方式使用 Web 服務,而非同步的 XML RPC 呼叫。


Other Layering Schemes#

其他分層方案#

作者的討論建立在三個主要層次上(展示層、領域層、資料來源層),但其他架構書籍也有各自的分層方案,都有其價值。

Brown 模型#

  • 五層架構:展示層、控制器/中介層、領域層、資料映射層、資料來源層
  • 在基本三層之間加入了額外的中介層
  • 對應到作者的模式:控制器/中介層 = Application Controller(展示與領域間的中介);資料映射層 = Data Mapper(領域與資料來源間的中介)
  • 作者認為中介層是經常有用但非必要的選擇——先以三層為基礎思考,再視需要加入中介層

Core J2EE 模型#

  • 層次:客戶端、展示層、業務層、整合層、資源層
  • 主要差異在於將展示層拆分為客戶端(桌面部分)與伺服器端(HTTP handlers、server pages)
  • 資源層代表整合層連接的外部服務

Microsoft DNA#

  • 三層:展示層、業務層、資料存取層
  • 與作者的三層對應相當直接
  • 最大的差異在於所有層都操作從 SQL 查詢產生的 record sets,使用 Data Transfer Object 在層間傳遞
  • 領域層以 Table Module 形式存在,資料來源層使用 Table Data Gateway

Marinescu 模型#

  • 五層架構,展示層拆分為展示與應用(Application Controller),領域也拆分為服務(Service Layer)與領域(Domain Model)

Nilsson 模型#

  • 使用預存程序較多的複雜分層
  • 同樣分離應用層與領域層
  • 預存程序層同時包含資料來源與部分領域邏輯

服務層(Services Layer) 從領域層中分離出來的做法,基於將工作流程邏輯與純粹的領域邏輯分開。服務層通常包含特定於單一使用案例的邏輯以及與其他基礎設施的通訊(如訊息傳遞)。是否需要獨立的服務層和領域層,視情況而定——作者認為這是「偶爾有用而非必要」的做法。