前言
這篇稍微紀錄一下這次讀本書第七篇的感想,這篇講很重要的幾個模型模式,我想記錄幾點: 組織業務邏輯的模式的模式,跨越邊界傳輸數據內的模型,另外補上 任務編排,另外第六篇之所以沒寫是因為表現層我還沒想到寫啥,主要跟UI,UX有關。
Agenda
- 業務邏輯的模式
- 任務編排
- 跨越邊界傳輸數據的模型
業務邏輯的模式
這篇基本上有滿滿的重點觀念,很多從以前三層式架構轉過來的模糊地帶,在這裡都可以有效的解答到。本文前面提到避免陷入 Pet-Technology (寵物技術),其實這兩年來基本上我就是陷入了,但也沒什麼不好,自我提升提升一下眼界,知道一下往後會有什麼技術,學習是學習,不代表一定要導入,我希望沒有人會問我”那你幹嘛學”,這種鳥話。在整個IT產業的過程,我發現IT人有某些特性,他們會未雨綢繆,也是很多人說得想太多,但大家都知道在IT世界裡面你必須得想多一點,哪怕提早想到,種比上線後出事來的好。所以身為一個IT人,我們認為先知道是好的,預防勝於治療。回歸本文,這篇講到很多模式: Transaction Script (事務腳本模式TS),Domain Model (領域模式DM),Anemic Domain Model (貧血領域模型 ADM)。
貧血領域模型
貧血領域模型是一個反模型,也就是在本文內容基本上不建議使用,會建議使用富模型,但是,沒錯本文也有說到如果你的是簡單專案,或是你的流程存取資料模型跟視圖模型要露出顯示的資料是一樣的。那本文內也說明並不完全不建議,這種情況下你可以使用貧血領域模型。還有就是CRUD這類專案也會適用。 貧血領域模型的特色是只有屬性,卻沒有行為。邏輯不再領域對象內,所有邏輯都放在對應的Service類上,在服務內妳會使用領域模型,訪問持久化(DB)。也就是你會透過ProductService去ProductRepository取得DB內的Product資料,回傳到表現層。Martin Fowler認為貧血領域違反了面象設計(OOD),把數據跟流程混在一起了,變成 過程式設計,Fowler也說別把貧血的對象當成是真正對象,貧血對象比較類似DTO的數據容器。最後這段在本文裡我覺得有再說反話的感覺,提到領域模型的優點變相說出不是領域模型的貧血模型的缺點:
- 經過分析後的對象,可以比較貼近實際的建模
- 領域視角邊界,可以降低領域專案跟開發人員的誤會風險
- 程式碼可讀性提高
- 更加貼近最終預期結果
Domain Model 領域模型
領域模型是一個實體,他有公共屬性,有可暴露方法。常使用POCO名稱。不是貧血那樣的。可參考微軟官方這篇內的POCO。
這圖顯式整體Ordering領域的結構類型
(eShopOnContainers 訂購微服務的領域模型結構)
這圖顯式Order aggregate這個領域的結構類型
(eShopOnContainers 訂購微服務的領域模型結構)
領域模型在某種角度上他是邏輯數據庫,也就是當你透過Repository取資料回來後將值對應到領域模型對象上,但這裡我有疑慮了,如果以Entity Framework的方式,關係型模型就是Entity,而Domain Model就是取出後需要映射的對象,我們在透過此對象做邏輯上操作。目前我使用的分層架構上,都是直接使用Entity當做貧血模型,也是關係型模型用來對應的DB的值。在CRUD內的專案這樣免去很多工。不過那是因為貧血領域模型適合這類專案,領域模型適合大型複雜領域的專案。
Transaction Script 事務腳本模型
事務腳本模式是一種最簡易的業務邏輯模式,他也是一種 過程式設計。擺脫OOD的方式,直接由用戶操作方式,來設計,每一個Request請求寫一個對應的方法,這個方法俗稱 事務腳本。事務講的是你所要執行的業務,而腳本指的是整個系統流程的操作。有種一條龍的感覺,目前很多專案都是採用這模式。很單純,多半會搭配貧血領域模型。這種模型我覺得很適合CRUD專案或是後台內部系統。TS可搭配Command Pattern,你須寫好一個介面Command,讓繼承此介面的類都有該方法,也實作該方法,而這個方法就是步驟流程(腳本)。這種Pattern優點是外面的呼叫該方法但不需要知道該方法的內容。我個人覺得TS模型適合表現層的Request不多的模式,因為很單純的就是界面點即觸發後續處理,也不用管後續做什麼,那是業務邏輯的事,客戶端根本不需要知道,而且可分類,每個類的腳色,在大話設計模式一書中,店員接受到客人的點餐要求後,只需要把餐點單據送到廚房即可,後續不需要管,而廚房只需依照餐點單據依序處理餐點,回傳給店員,雖然這種方式稍嫌慢了點,但他就是一個模式。這也凸顯使用該模式的限制,一個廚師,一個店員適用,當我有多個廚師,一個店員呢? 餐點單據送到廚房,廚師接手,如果是領域模式,拆分更細,接到餐點單據是一個服務,該服務分析告知有一份蔬菜,3分牛肉,5份羊肉,7杯酒水。每個負責該食物的廚師各自處理自己的流程,再返回,每個返回的速度不一,提前完成的可以提前依照餐點單據碼返還給店員,讓店員發送。多店員也是以此方式以此類推。但因為TS的腳本是一整個的流程,所以設計上就慢了一點,但還是有辦法解決的。不過對簡易的操作是還可以的。
任務編排篇
這篇由於需要後續篇幅的驗證,在還沒完全搞懂本文,暫時不細講本文內容。目前先記錄遇到的疑問,也歡迎鞭打,能搞懂比較重要。
Q: 我搞不清楚應用程序層,他是Controller嗎? 還是邏輯的Service? A: 我覺得本文這裡少寫,真的嗎? 應該是我自己看不懂吧,我以前一直認為Controller就是應用程序層,Service結尾的都是邏輯層,對沒錯,但那是在多層式架構上。在DDD內,Service結尾的是有邏輯沒錯,但大部分邏輯也在Domain Model方法內。本文顯示的應用邏輯層跟領域層內的服務很容易搞混,因為他們都是Service,一個是應用層,一個是領域層,所以我想進一步區分,將應用程序層內的都歸屬到Controller上。
- 表現層 –> 應用程序層: MVC的View 到 Service,連接點是MVC的Controller (本書內容)
- 表現層 –> 應用程序層: Vue 到 WebAPI的Controller就是我的應用程序層,也是連接點 (我使用上的定義)
- 應用程序層(領域層) –> 數據訪問層: Service到Repository (這層流程命名上一樣)
- 流程: Vue (表現層) –> Controller(應用程序層) –> Service (領域層-領域服務) –> 使用到Domain (領域層-領域模型) –> Domian或是Service方法 –> Repository (數據訪問層)
跨越邊界傳輸數據
在這段的部份我打算標明每個model的名稱跟使用的一些特性來紀錄,因為這很容易搞混,很多時候搞混的點就是,表現層需要的顯示資料其實就是對應Domain的Model,那我還需要配置這個多個DTO或是ViewModel嗎? 答案是可以不用,我覺得有些區分可以是更高層級,就是多半在你的專案內,如果很小的專案,他只需要CRUD,那你會只需要單一物件模式,也就是最一開始的MVC架構設計,但是隨著需求增加,架構上往往不會只有單一物件,複雜度越高越是需要區分。以目前的MVC來看,他的M其實已經慢慢變成是ViewModel。
模型
View Model 視圖模型
- 給Presentation Layer (表現層)用,在不同的裝置上,可以有不同的ViewModel去呈現
Domain Model (領域實體)
- 如果使用在Application層跟邏輯層或是其他Service層之間是OK的
- 如果傳輸在物理層,你會需要序列畫,那就會有問題,Method就露出了,另外是Domain Model如果有循環繼承的物件,序列化後會無限循環的問題
- 有延遲加載屬性也會照程問題,因為有時候數據還沒加載,就被另一層拿去沿用
- 在往後的CQRS中,使用單一模型就有打破一致性風險
Data Model 數據模型
- 對應DB資料的數據模型
DTO 數據傳輸對象
- 專門使用在跨物理層
- 可攜帶多塊數據
- 可序列化
- 沒有方法,只有Get,Set
- 不須單元測試
- 本文想廣泛使用他,在不同邏輯層之間使用他
EX: 當一個傳輸訊息包含訂單跟客戶訊息,但卻只需要部份的資訊而不是整體領域實體Entity,這時可以透過DTO簡化數據容器,包含必要數據即可
- 當你有很多需要轉換Domain Model數據到DTO數據,可以使用AutoMapper工具
- 但是過多的DTO其實是有缺點的,AutoMapper工具是方便,但是你還是得維護他,而他也是需要爬完整個實體圖從儲存獨到內存內,另一個解法,也是我目前常用的,除非跨物理層,我才建立DTO,不然使用IQeryable對象(LINQ)來反傳塞值也不是太糟,畢竟可以減少DTO
模型Q&A
- Q: Domain Model 不是 Entity嗎?
- A: Entity應該是對應DB資料的,但是那是由Entity Framework這類DB導向的方式設計,在DDD內我一直在懷疑Entity名稱跟他的定位,這應該是我搞混多層架構搭配DB關聯資料方式轉過來的誤解,上一間公司的架構是貧血領域模型,也就是這樣領域實體內沒有方法,只有屬性,當中搭配TS模式,他的Entity就是DB對應,但DDD內的Domain Model的Entity是Domain Model的。舉一個例: ShoppingCart。這個模型我認為他不該出現在DB,但他會出現在Domain Model。
注意
模型跟服務兩個層級都是屬於領域層,但該怎麼區分他們兩個。如果屬於該模型的,類似基本CRUD我都會放在該模型方法內,但是跨模型的方法,或是不知道該放哪個方法,我都會放到Service層內。以前公司使用的貧血領域模式就是使用Service層Only,因為Domain Model內的模型是沒方法的。