「Entity 大戰 Entity」 — 談 Bounded Context 與 Annotation
前陣子收到一位讀者的來信,他針對我的書「你就是不寫測試才會沒時間」的內容,問了我一些延伸的問題。
我覺得這些問題應該是大部份要在工作上開始加入單元測試的人都多多少少會遇到的,所以直接寫成文章,直接回應該讀者的同時,也讓部落格的其他讀者,如有類似問題的,也能一起得到解答。
此為第二題:
筆者在書中沒有提到 Spring Boot 的 @Entity Annotation 是有原因的。首先這是框架限定的用法。雖然書中範例都是 Java,但筆者不想把單一框架當成限制,因為讀者不一定也是用 Java,就算用 Java 也不一定用 Spring Boot,就算用 Spring Boot 也不一定用 JPA。
你的 Entity 不是我的 Entity
事實上,JPA 的 @Entity Annotation ,目的在於幫開發者省下一些下基本 SQL 的時間。因為 JPA 是「框架」的一部份,所以他完全不認識你的 Domain。他就只是一個「把 Table 方便轉化為 POJO」的工具。
然而,你的 Domain Model 中的 Entity 身份不同。這裡的 Entity 是指你的業務邏輯最核心的一塊。例如,你可以把銀行營運環境中的用戶用 User 來表現、帳戶用 Account 來表現、分行用 Branch 來表現…,等。
這些 Entity 有血有肉,有屬性有狀態,能自己做自己該做的事,例如 user.chaneName(“kuma”)、account.withdraw(100)、branch.move(“new address”) 之類的。在物件導向的程式設計中,我們傾向用這樣的方式來完成工作。
因為物件狀態,而狀態可能改變,所以要在適當時機來「儲存」這些狀態。這樣的行為也有人稱之為「持久化」,而 JPA 正是 Java 世界很常拿來進行持久化的工具(框架)。
所以,簡單來說:
- DDD 或是 Clean Architecture 的 Entity,是模型的一部份,是我們拿來模擬真實生活中,真實存在的物體的一種方式。
- JPA 中的 @Entity , 是框架拿來減少開發者手動下 SQL 的一種工具。
一樣叫 Entity,在不同的 Context 底下,代表的意義不同。
一樣會怎樣?
來信讀者問了,一樣會怎樣?他們專業到處都是這種寫法,也沒發生什麼事,不是嗎?
是的,功能這種東西,你只要寫得出來,當然不會怎樣。至少,在「功能」上,是不會發生什麼問題。
但是,正如 Kent Beck 的 Tidy First? 一書中所說,設計這東西其實是非功能需求,跟功能沒什麼必然關係,所以,兩種不同 Context 下的同一名稱的東西,在你的系統中被綁在一起,只會影響你的設計而已。
再講具體一點,這樣的設計只會造成兩種結果:要嘛領域模型被 DB 綁架,要嘛 DB 被領域模型綁架。
例如:當你的 User 分成員工、主管、老闆三種,每個人身上該存的屬性各有不同。此時 DB 不得不因為這樣的領域模型而多建很多欄位,再依 User 類別而讓特定欄位為空(null),這對 Domain 來說沒差,但對 DB 來說就不是很經濟了。
再例如:當你的 Customer 有上下線的關係,Customer 的上線跟下線也都是另一個 Customer,這時對程式來說,你可能會比較想要有一個 customer.getUpLine() 與 customer.getDownLine(),並直接得到另一個 Customer 物件。但,這件事一般的 RDB 是辦不到的,你頂多可以把上線與下線的 ID 分別存在表中,而無法直接存另一個 Entity。
以上兩個例子只是滄海一粟,在你的系統中一定存在更多把兩種 Entity 混在一起使用時,遇到 OOP 系統與 RDB 本質上不同,而無法直接 mapping 的情形。
這就是筆都不建議各位直接把 @Entity 這個 Annotation 直接拿進來當成 Clean Architecture 的 Entity 的原因。
Reference
Kent Beck, Tidy First?:https://www.amazon.com/Tidy-First-Personal-Exercise-Empirical/dp/1098151240
More about Me
- 更多軟體工程相關討論:https://www.facebook.com/kukumamaya
- 我的YouTube 頻道:https://www.youtube.com/@yu-songsyu6598
- 沒時間寫測試?沒時間重構?你就是不寫測試才會沒時間: https://www.tenlong.com.tw/products/9786263332645?list_name=lv