#讀書趣(1):Patterns of Enterprise Application Architecture

 



今天重看這本電子書:【Patterns of Enterprise Application Architecture】

我分享一下我以我現在的觀點重讀過這本書的觀點,因為上一次讀這本書已經 10 多年前,當時對於許多實作內容其實懵懵懂懂也都只是在專案上使用,由於當時專案非常忙碌(幾乎每日加班到晚上12點趕最後一班捷運 XD)對於書上提及的 Patterns 也沒太多時間去深入探討與理解。
今天,我就以我現在的經歷、對軟體開發技術的認知 + 實作經驗 來重讀 與 介紹本書籍。

我一個一個章節來為大家分享讀書心得:

Chapter 1 Layering
對於『分層』定義不熟悉的朋友,建議熟讀這章節。
Chapter 2: Organizing Domain Logic
前面幾章就屬這章節最有意思,文中提到隨著商業邏輯的越來越複雜,不管你使用哪一種軟體架構當你要添加功能其實就越來越困難,因為很難有一種能夠衡量領域複雜性的方法存在(很有意思、這到目前也都還是如此)不過在我最近讀了 DDD 相關書籍後這部分有所改觀了!

Chapter 3 Mapping to Relational Database
這個章節說明一般應用程式存取關聯式資料庫常用的策略,如果您是剛入門不久的 Developer 真的建議好好讀這篇。
這裡提到早期的大型主機後來不盛行被SQL取代的原因,開發人員不夠了解SQL查詢與底層效能索引調教之間關係,導致後續許多問題的發生。接著,就是一些實務上操作 SQL 一些策略,像是一個特定領域 Domain 就與該特定領域 Table 互動、另一種,是 Customer 應該隔著一層 Mapper 才操作 SQL、而不是直接操作,Customer 應該只負責特定領域,不該了解太多 create/load/save 的細節。接著,就是我很熟悉的 O/R Mapping
其實、這章節對我來說並沒有學到新東西、因為我現在實作遠超過這些。

Chapter 4: Web Presentation
在現在 Web 蓬勃發展的現代來說,這個章我就真的沒學到什麼東西,書中使用的 技術 有些老舊了,對基本 Web 不熟悉者可以參考看看。

Chapter 5: Concurrency (by Martin Fowler and David Rice)
本章節也建議開發者一定要看,如果你開發的都是資料庫相關的商用系統,ACID、Patterns for Offline Concurrency Control 真的都只是基礎而已!對我來說是沒學到什麼新東西,都是家常便飯。

Chapter 6: Session State
關於狀態、在 Web Stateless 環境裡,領域層還是有自己狀態,在交易還未結束時妳都是在處理這些狀態,但是 Web 沒有狀態... 很繞舌 XD
自己看看吧!這裡就沒提到新欣議題像是 Cache 等,畢竟是很久以前的書了。

Chapter 7: Distribution Strategies
這些分散式的策略也是我們在實作 Scale-Out 或 Cluster 的必備知識了。還是建議可以看看這篇。

Chapter 8: Putting It All Together
不過經典就是經典,在 2002 當年就有些遠見與看法 Martin Fowler 果真是大神,技術的詞是很舊了,但是觀念到現在也都還是沒變,有趣的是,筆者當時就看好 Java 與 .NET 這兩個平台,並說這兩個平台就是未來企業應用程序開發的最常見平台,果然!現在就是 XDDD
而看到這描述著 Transaction Script (110), Table Module (125), and Domain Model (116). 這三種作法最容易時做的就是 Transaction Script 了,這就像是微軟當年的 TransactionScope 一樣,什麼都包起來、什麼都請 Database 來做,這最簡單了,但是隨著你的領域越來越複雜,最後可能只有 Domain Model 能夠輕鬆解決你的問題,這根本是 DDD 的先驅阿,領域驅動開發在當年的 Martin Fowler 心中早就萌芽了?還是 Eric Evans 在創出 DDD 時、與 Martin Fowler 有聊過?我記得他們本來就都是朋友了 XD
更有意思的是,Marting Fowler 當年覺得微軟太過炒作 Web Service,因為除非有需要,實在沒有理由要將不同的應用程式拆分為不同的 Web Service,而且使用非同步通信是未來趨勢!真是有遠見!!!

Chapter 9: Domain Logic Patterns
本章節將用範例來介紹上面這三種作法 Transaction Script (110), Table Module (125), and Domain Model (116),在這裡關於領域模型提到 OO 對應到數據庫好壞、不同的職責應該分離、領域模型咬解決的是商務邏輯、不是與 Table 一對一的操作,這邊還未牽扯到資料庫,所以在商業流程裡就會有識別自己 Entity 的問題存在,這根本就是 DDD 的前身。這邊的 RevenueRecognition 的 Java 範例其實就是 Domain 物件 "非貧血模型" 的範例。
至於 Table Module 我個人不太喜望這樣的設計模式,但是為什麼只有 Table Model 要用 .NET C# 來說明呢?

初步簡單結論:
讀到這裡,雖然一些技術很老舊,不過 Martin Fowler 在 2002 年當時的思想就很遠見,觀念即使到現在也不退流行,果真是大師!

接著在開始讀第十章,第十章的內容就比較硬一點了...

Chapter 10: Data Source Architectural Patterns
這個章節很有意思、主要圍繞在應用程式在存取資料時,資料表層如何封裝、其實也就是 DAO (Data Access Object) 的設計樣式。

Example: Table Data Gateway


前面的 Table Data Gateway:
這裡使用 .NET C# 的 ADO.NET DataSets 來展示一個 Person Gateway 範例,但作者表明這不適合 .NET,因為這其實是將早期 ADO 的 RecordSet 技巧拿來用而已,而 .NET 用 IDataReader 介面來存取資料。這些只是對 Database 做 CRUD 的簡單實現,必須說這真的是很舊了。
圖:Using ADO.NET Data Sets

再來的 DataSetHolder 其實就是後來 ADO.NET 的 DataAdapter 的具體實現方式,其實包括 Person Finder 概念是商務邏輯不該與 SQL 牽扯在一起,這樣會讓商務邏輯難以維護,概念都非常簡單,所以 Gateway 就是對資料的封裝,就像是後來 Repository Patterns 一樣,而 Person Finder 就是去操作它的商務邏輯。

Example: A Person Record (Java)
這裡就是以 Java 來實作 insert/delete/upate 的做法,Record 感覺比較像是 與 資料參雜 一起撰寫的 Entity 實體,他不是貧血模型,但是與 SQL 混和在一起,這段在現在其實還會再拆。

Example: Active Record
我覺得所謂的 Active Record 這有點像是 O/R Mapping 映射,它必須有欄位能與 Database 一對一,只不過它還有對單一 Record 的操作與存取的邏輯在上面,像是:insert/delete/update,你說它是 Domain Model 嗎?雖然它具有邏輯,但又像是實體 Entity,以現在觀點來說,它太複雜了,因為負責的事情太多,商業邏輯也是與 SQL 牽扯在一起。

Example: Data Mapping


這就是上面 Active Record 的改良版,將實際對 Database 的 (insert/delete/update) 往後挪至 Person Mapping,因為它本來就不是 Domain Model 應該負責的工作。

Chapter 11: Object-Relational Behavioral Patterns
Unit Of Work:


這個 Unit Of Work 前面寫了很多廢話(誤)XDDD,其實長話短說就是,應用層在存取 Database 時,不應該需要資料就去拿,就像前面的 Data Mapping 或 Table Data Gateway 等概念,且都去資料庫查沒什麼不對,但是不應該將同一筆資料重複讀取兩次在不同的地方,你得有一個控制先後次序的地方,加上對 Database 太大量的存取次數不是好事,即便這些資料量都非常小,長久以來只是造成 Database 的負擔。
因為你還得控制交易、與存取的先後次序、這至關重要,因為妳還要控制(樂觀鎖定 / 悲觀鎖定),已因為剛剛說過的 Data Concurrency 等問題、或者是分散式環境你的交易問題,只是分散式環境有可能跳脫出 Unit Of Work 的範圍了。
所以,存取 Database 應該有個統一的地方 ,因為你還得控制優先順序、像是典型的 (Master/Detail 表格) 就是一種,包括地在同一個交易內要更新的資料,你不該管不同的 Request 內的處理,他們並不相關,也不應該受影響,這些都應該統一入口由 Unit Of Work 來處理。這麼一來你也才有辦法追蹤這些變更!
圖、使用 Unit Of Work 控制資料的存取:


Unit Of Work 的初步小結:
這與我長久以來在專案中實作的 Unit Of Work 大同小異,Unit Of Work 除了控制交易事務的大小外、資料整合性、先後次序、追蹤變更等,這些都是它的主要工作。

Identity Map:
我反覆看了多次,這個模式幾乎就是 DDD 的 Entity 前身阿,概念上幾乎是一致的,我們不管是在處理商業邏輯時,或者說是將資料從資料庫讀取自 UI 層也好,資料總是有一個來源,而這個來源必須確保資料的一致性,在商業邏輯層是唯一的存在,若同時有兩個地方撈了相同一筆資料,兩個地方也都更新了資料,那兩邊也都更新回去即會發生 Data Concurrency Exception。
所以,同樣的道理,在 Entity 實體如果能識別 Instances 的 Identity 唯一性,將避免這種問題的發生。

Lazy Load: AN object that doesn't contain all of the data you need but knows how to get it.
這也是我們在開發上早已使用多年的技法了,這在 Clean Code 裡也有介紹,就是物件的內容或屬性其實很多情況都不應該在該物件被初始化時就跟著被初始化,應該在需要它時才初始化 (Lazy Load)。

Example: Virtual Proxy (Java)
這也是當中章節看到的,它先定義一個存放 List 的 Proxy 類別,透過介面實作 Load 方法產生需要的 Product List 資料,並在 VirtualList 類別裡透過 getDataSource() 方法取得這 Proxy 資料,不過要注意的是,主要外部存取來源還是 SupplierMapper 類別的 DomainObject 的 doLoad() 方法。

Example: Using a Value Holder (Java)
這段就是將上面的範例加上 Lazy Load 而已,沒什麼特別。

Example: Using Ghosts (C#)
這也是一個相當有趣的 Mapper,這其實就是結合以上所有集於一身,所謂的 Ghost 就是還會形成實體,這邊增加了 enum 型態表示出(GHOST, LOADING, LOADED)三種狀態,這裡的 MapperRegister 實作 IDataSource ,其實就是 IRepository 的概念、註冊一個資料來源,用 Mapper 是對應,但又要對 EmployeeMapper 封裝住來源,對 EmployeeMapper 來將是一個抽象的實作,因為 EmployeeMapper 與 MapperRegistry 的中間又隔著 Mapper ,因為真正實作 Load 的是 Mapper。這裡對應到現今常見的 ORM 框架會發現,其實就是現在常見的 O/R Mapping 的底層實作方式,所以這裡的 Domain Object 比較偏向是 O/R Mapping 的實體 Entity。

待續..... XDD

總結:

最後面第 10/11 章節所提到的,都是早期我們在設計 Data Access Layer/DAO 時會遭遇到的問題的具體與常見的實作方式,早期 GoF 三人組還未整理出 Design Patterns 的時候、而且 O/R Mapping 相關框架也還沒那麼盛行時,底層都是靠自己來實作的,在當時的確都使用這些解決方式,整體來說都很清晰明瞭,沒有什麼太複雜的地方,畢竟是很多年前的書籍了。
不過我 20 年前當時倒還沒用到這裡的 Domain Object 的概念,如果 2002 當年有看到這本書就好了,哈哈。
本書的基本概念到現今其實也都還適用、也不退流行,有興趣往 Developer 發展的朋友,Martin Fowler 的書還是值得一讀,了解這些設計模式的來龍去脈是本重要的!


留言

這個網誌中的熱門文章

軟體工程師 - 成長的 10 個階段

常見的程式碼壞味道(Code Smell or Bad Smell)

什麼是 gRPC ?