领域驱动和MVVM应用于UWP开发的一些思考
領域驅(qū)動和MVVM應用于UWP開發(fā)的一些思考
?
0x00 起因
有段時間沒寫博客了,其實最近本來是根據(jù)梳理的MSDN上的資料(UWP開發(fā)目錄整理)有條不紊的進行UWP學習的。學習中有了心得體會或遇到了問題就寫一篇博客記錄一下,方便后面查詢。不過前幾天在園子里逛看了幾篇領域驅(qū)動的文章,突然發(fā)現(xiàn)領域驅(qū)動設計的有些地方對我有了很大的提示。在之前用WPF做桌面開發(fā)時,使用MVVM可以把View和Model很好的解耦,但在處理數(shù)據(jù)持久化的時候并沒有找到一種特別好的方式。我之前的做法是把ADO封裝了一層SQLHelper用于處理數(shù)據(jù)庫操作,解耦了數(shù)據(jù)庫操作和具體數(shù)據(jù)庫類型的依賴,當然了前提是數(shù)據(jù)庫操作是基于ADO.NET實現(xiàn)的。對于返回的DataTable,DataSet等在Model的方法中轉換為Model的實例。例如有一個對象是User,要從數(shù)據(jù)庫中獲取所有用戶我一般是在User類中寫一個靜態(tài)方法,User.GetAll(),這個方法會使用SQLHelper執(zhí)行SQL語句,獲取DataTable,然后把DataTable轉換成IEnumerable<User>并返回,這算是人肉ORM吧。為此我還寫了個代碼生成工具,把類中需要的屬性和屬性對應的數(shù)據(jù)庫表字段名稱、類型等信息設置好,可以直接生成代碼實現(xiàn)映射,省去了手動輸入的麻煩,不過能生成的數(shù)據(jù)庫操作類型十分有限。但這樣做存在的一個很大的問題就是Model變得十分臃腫。雖然領域驅(qū)動提倡用充血模型,但我那種Model應該算是過度充血了吧。而且以現(xiàn)在的觀點來看,這種方式把基礎層(數(shù)據(jù)庫操作)、ORM(ADO對象映射為User對象)、Model緊緊的粘合在了一起,如果突然有一天跟我說數(shù)據(jù)操作必須用WebAPI,我能做的大概就是把之前的代碼Ctrl+K+C,然后重新從WebAPI獲取數(shù)據(jù),然后從JSON映射到Model吧。如果說需要數(shù)據(jù)庫和WebAPI混用,我的Model將會變得更加臃腫。
領域驅(qū)動設計通過Repository實現(xiàn)了業(yè)務領域和基礎層的數(shù)據(jù)庫操作之間低耦合,結合之前MVVM模式帶來的View和Model的低耦合,又恰巧最近在學習UWP開發(fā),所以有了把MVVM和DDD在UWP開發(fā)中實踐的想法,于是也有了這篇文章。在這里需要說明的是我也是剛開始接觸領域驅(qū)動,其中的很多概念還沒有接觸到,看的比較多的就是Repository,畢竟數(shù)據(jù)持久化是我之前的痛點。后面我也會把數(shù)據(jù)驅(qū)動相關書設置為我的床頭書,打算認真看一下,可能隨著學習和對領域驅(qū)動的了解有些想法會發(fā)生改變,有了新的體會后面也會寫文章,這篇文章主要記錄了這個時期我對領域驅(qū)動的粗淺理解,有不對的地方也希望多多指教。
0x01領域驅(qū)動和MVVM模式
我看到的關于領域驅(qū)動的文章很多都是討論實踐于ASP.NET MVC的,而且?guī)缀醵及袿RM當作了必選項。而我大多數(shù)時間是做桌面開發(fā)的,所以看到領域驅(qū)動的第一反應是與MVVM模式的結合。那么MVVM和DDD有沒有可能結合起來呢。
?
MVVM核心的三個部分是Model、View、ViewModel,重點解決的是通過ViewModel實現(xiàn)Model和View的低耦合(MVVM模式解析和在WPF中的實現(xiàn)),但有一個問題是沒有解決的,那就是業(yè)務邏輯和數(shù)據(jù)持久化要如何處理。有人認為業(yè)務邏輯應該放在ViewModel中,Model應該是POCO對象,用于數(shù)據(jù)顯示,業(yè)務邏輯可以通過ViewModel和Service實現(xiàn)。在這種觀點中Model更偏向于DTO的存在。還有一種認為業(yè)務應當聚合到Model之下,這樣更OO一點,難以聚合到Model下的就寫成Service,我就是屬于這種觀點的,甚至于Model相關的所有業(yè)務邏輯和數(shù)據(jù)操作也全都聚合到了Model下。
而領域驅(qū)動注重的是業(yè)務領域這一層的分離,簡直就是對MVVM的一種最好的補充。現(xiàn)在要再有人問MVVM中業(yè)務邏輯該放在哪里,我會毫不猶豫地告訴他業(yè)務邏輯要放在領域?qū)印?/span>
從分層的思想來看,領域驅(qū)動設計為四層架構,分別是表示層、應用層、領域?qū)雍突A層。
?
接下來要討論的領域驅(qū)動和MVVM的結合也是以領域驅(qū)動的這四層架構為基礎的,把MVVM的三個核心概念融合到這四層架構中。下面我就結合MVVM談一下自己對這幾層的一些認識。
0x02 表示層和應用層
首先表示層這個是最明確的,就是用戶看到的那一層。對于桌面應用來說就是窗口,對于Web應用來說就是HTML頁面。對應于MVVM模式中的View。
然后說下應用層,領域驅(qū)動設計認為應用層是非常薄的一層,應用層調(diào)用下面的領域?qū)雍突A層來實現(xiàn)功能。這個對于主張把業(yè)務邏輯聚合到Model下的我來說沒什么違和感,在MVVM中,應用層對應的就是ViewModel。首先必須說明的是這里說的ViewModel和MVC中的ViewModel并不完全是一個概念,這兩個ViewModel都是對View的抽象,但在MVC中引入ViewModel主要是為了與View對應,減少View的邏輯操作。這個ViewModel是對View中數(shù)據(jù)的抽象。在MVVM中ViewModel除了對View的數(shù)據(jù)抽象外還包含了對用戶交互和功能等行為的抽象。例如MVVM中的ViewModel還需要處理用戶的點擊,輸入等操作,MVVM中的ViewModel完全可以不包含業(yè)務細節(jié),實現(xiàn)為很薄的一層。例如把大象放進冰箱大致分三步,ViewModel中的操作就是:
Fridge.Open();
Fridge.PutIn(elephant);
Fridge.Close();
至于冰箱空間不夠放不下大象了需要拋出異常,能放下的話把大象放在哪里更合理,放入大象后是不是需要擺放空間優(yōu)化等等都聚合到了Fridge這個Model中,ViewModel只是調(diào)用這些功能。這也符合領域驅(qū)動中提倡的充血模型。
在領域驅(qū)動設計的四層架構中,我們可以看到表示層是可以調(diào)用下面三層的,也就是說表示層可能對下面三層都會產(chǎn)生依賴。這樣當表示層發(fā)生變化時下面三層中有的地方可能也需要做出改變,同樣業(yè)務邏輯的一些變化可能也會影響到表示層。而在領域驅(qū)動中使用了MVVM模式后,表示層(View)依賴于ViewModel,對于ViewModel以下都不直接產(chǎn)生依賴。
0x03 領域?qū)雍突A層
View和ViewModel都找到了對應的層了,那Model對應哪一層呢。這個問題就不好回答了,這個得看對Model的理解了。如果把ViewModel看作很薄的一層應用層,Model使用貧血的POCO對象,那么這時候MVVM中的Model更偏向于DTO的存在,這時候在領域?qū)有枰辛硗獾木酆狭藬?shù)據(jù)和邏輯的模型。如果把MVVM中的Model看作數(shù)據(jù)和業(yè)務聚合的充血模型,那么這個Model可以當作業(yè)務領域中的Model,這時候如果需要的話可以考慮加入DTO。不過感覺這么考慮有點太教條了。我覺得可把領域?qū)幽P涂醋鱉VVM中的Model。
基礎層這個是MVVM中沒有明顯說明的。如果我們把領域?qū)拥哪P涂醋鱉VVM中的Model那么基礎層是直接可以拿來用的,不存在任何沖突。在嘗試將MVVM和領域驅(qū)動結合后大概是下圖這種感覺,表示層只依賴于應用層。
?
0x04 關于Repository
之所以把Repository拿出來單獨說,就是因為領域驅(qū)動最開始吸引我的就是Repository很好的解決了MVVM中沒有涉及到并且長期困擾我的數(shù)據(jù)持久化的問題。此外另一個原因是看到很多文章討論Repository應該屬于哪一層的問題,這其中包含了Repository接口的定義和實現(xiàn)等。這里我也談一下我個人的一點看法。
從領域驅(qū)動設計的四層架構中我們可以看到,領域?qū)邮且蕾嚮A層的,而基礎層并不依賴領域?qū)?#xff0c;因為如果有兩層是互相依賴的,那么分層就毫無意義了。這種依賴的具體表現(xiàn)是什么呢?最具體的表現(xiàn)就是基礎層和領域?qū)臃謩e存放在不同的程序集中,領域?qū)映绦蚣没A層程序集,基礎層可以在沒有領域?qū)拥臅r候編譯通過。基于這個結論來看Repository,Repository接口的定義和實現(xiàn)都是依賴于領域?qū)訉嶓w模型的,這個是無法避免的,所以Repository無論接口的定義還是實現(xiàn)都應該放在領域?qū)印嶋H上從訪問數(shù)據(jù)庫讀取數(shù)據(jù)到最終轉化為對象列表,這個過程跨越了基礎層和領域?qū)?#xff0c;這個過程中包含著兩個關鍵動作,第一個是從數(shù)據(jù)庫中讀取數(shù)據(jù)庫數(shù)據(jù),第二個是把原始數(shù)據(jù)映射為領域?qū)訉嶓w對象。其中第一個動作屬于基礎層,第二個動作屬于領域?qū)印>唧w到代碼我個人的理解就是在領域?qū)又胁粦摮霈F(xiàn)任何SQL語句以及明顯的數(shù)據(jù)庫相關的東西(例如SqlConnection)。根據(jù)這個理論來舉例,讀取Users表中的內(nèi)容并獲得User對象列表。在基礎層中提供針對數(shù)據(jù)庫的通用的操作,操作返回ADO對象,不依賴于領域?qū)?#xff1a;
public DataTable GetUserTable() {const string SQL = “SELECT * FROM Users”;Using(var con = new SqlConnection(ConnectionString)){var cmd = new SqlCommand(sql,con);var da = new SqlDataAdaptor(cmd);var dt = new DataTable();da.Fill(dt);return dt;} }在領域?qū)又卸xRepository接口并實現(xiàn),返回領域?qū)訉ο骍ser
public IEnumerable<User> GetAll() {var dt = GetUserTable();foreach(DataRow row in dt.Rows){yield return new User{Name = row[“name”].ToString(),ID = (int)row[“id”]}} }這樣領域?qū)訂雾椧蕾嚮A層。
之前看到有文章討論把Repository接口定義放在領域?qū)?#xff0c;接口實現(xiàn)放在基礎層,這個是不符合領域驅(qū)動的四層架構設計的。因為基礎層中對Repository的實現(xiàn)依賴了領域?qū)印5绻患m結于這個四層架構,或者說在實際項目中領域?qū)雍突A層不需要分別放到單獨的程序集中,這么做也是可以的,而且這么做領域?qū)訒@得更“純凈”,畢竟從數(shù)據(jù)庫到實體的映射不能算業(yè)務邏輯。還是那句話,模式和設計是一種通用的指導方向,最終還是要服務于特定場景,沒有絕對的對錯,只有合適不合適。
0x05 引入ORM后的問題
ORM的作用就是把具體的數(shù)據(jù)庫操作從業(yè)務邏輯中抽取出來,編寫業(yè)務邏輯時不需要再考慮具體的數(shù)據(jù)庫操作,把基礎層和領域?qū)拥墓δ芊庋b到了一起,這和Repository作用十分類似,可以說ORM是Repository的一個子集。這看上去似乎是很好的,在數(shù)據(jù)庫操作時直接使用ORM來代替Repository就可以了。但實際中存在的最大問題是ORM返回的實體對象是對數(shù)據(jù)庫表的抽象,一般是POCO對象,而Repository中返回的領域?qū)訉ο笫蔷酆细?#xff0c;聚合根和ORM返回的實體對象不一定是完全對應的,而且領域?qū)訉ο笫浅溲摹T谀承┣闆r下ORM返回的實體對象可以直接拿來作為領域?qū)訉ο笫褂?#xff0c;這自然是好的,但當不能直接使用時就需要轉換為領域?qū)訉ο蠡驅(qū)RM返回的實體對象進行功能上的擴展。
0x06 以上理論在UWP中的實踐
思考了一大堆理論連我自己都信了,但實踐才是檢驗真理的唯一標準。所以我打算新建一個測試用的UWP應用檢驗一下。記得學習MVVM那會,要用MVVM開發(fā)我一般會新建一個項目,然后建三個文件夾:View,Model,ViewModel,但為了能充分體驗那種低耦合和單項依賴的感覺,我曾在一個解決方案中建了三個項目,分別叫View、ViewModel和Model,其中View引用ViewModel,ViewModel引用Model(雖然在View最后生成的文件夾中我們看到了Model的dll,但View并不直接依賴Model)。項目是不能互相引用的,也就是單向的依賴。所以這次在檢驗自己理論時,我仍然用了這個方法,根據(jù)領域驅(qū)動四層架構的依賴關系,在一個解決方案中建了四個項目:View(表示層),ViewModel(應用層),Domain(領域?qū)?#xff09;,Infrastructure(基礎層),其中Domain中有個Model文件夾存放領域模型,也是MVVM中的Model。VS解決方案中項目的排列是按照字母順序的,所以項目存在的順序并不代表他們之間的依賴關系。
?
這樣MVVM和DDD中的幾大樣算是全了,可以開始了。計劃是做一個類似記事本一樣的東西,可以添加標題,內(nèi)容,并進行分類。應用會記錄添加時間和最后編輯時間。業(yè)務邏輯簡單,需要數(shù)據(jù)庫操作,所以看了下UWP的數(shù)據(jù)庫操作,然后就被啪啪啪打臉了。
UWP貌似只支持SQLite本地數(shù)據(jù)庫,不過SQLite也行啊,反正下幾個dll引用一下,數(shù)據(jù)庫操作封裝到Infrastructure,實體映射封裝到Domain的Repository,有強大的理論武器,怕什么。結果看了下UWP中的SQLite操作然后就臉腫了,UWP中SQLite數(shù)據(jù)庫操作不是基于ADO.NET實現(xiàn)的,微軟自己包裝了一套叫SQLitePCL,實在太簡單易用了。兩行代碼就執(zhí)行完數(shù)據(jù)庫操作,但獲取的數(shù)據(jù)并不是DataSet或DataTable那種數(shù)據(jù)列表,只能獲取SQLite對象然后一行一行讀取數(shù)據(jù),或者自己封裝成DataTable那樣的對象,返回到領域?qū)?#xff0c;再由領域?qū)佑成錇轭I域?qū)訉ο蟆N沂怯卸嗟疤鄄艜敲醋霭?#xff01;好吧我真的那么做了,只是為了試一下分層,以后UWP開發(fā)中絕對不會第二次這么做了。以后在需要SQLite數(shù)據(jù)庫操作時直接在領域?qū)又蝎@取數(shù)據(jù)并映射成領域?qū)嶓w對象,好吧,在領域?qū)又谐霈F(xiàn)了SQL語句,這臉打的,不過我有法寶:任何設計都要看場景!UWP中真的沒有必要把數(shù)據(jù)庫操作放到基礎層,可以把UWP中的SQLite看作已經(jīng)封裝好了的基礎層的功能,就差一條SQL語句當參數(shù)了。當然UWP上也有比較成熟的ORM工具,不過我沒有使用。
0x07 實踐后的想法
果然實踐出真知。
還有就是由于剛開始學習UWP,很多地方不太熟悉,有些希望達到的效果實現(xiàn)起來比較慢,所以這個實例最終還沒有做完。后面邊學邊做吧,牽扯到的一些技術問題都解決了估計也就入門了。另外在使用之前自己寫的簡易的MVVM框架去實際開發(fā)UWP應用時也發(fā)現(xiàn)了框架的一些不足,例如頁面導航用到Frame,所以在ViewModelBase中加入了Frame方便在ViewModel中導航,也體會到了設計時顯示測試數(shù)據(jù)的重要性,無需運行就能看到數(shù)據(jù)顯示的樣子,可以直接在設計界面觀察效果。為此加入了ViewModelLocator,在ViewModelBase中加入了InitTestData()和InitRealData()等,根據(jù)是不是DesignMode加載不同數(shù)據(jù)等等,這些等后面單獨寫一篇文章吧。
看了領域驅(qū)動的一些文章后對WPF開發(fā)也有很大的啟發(fā),后面再做項目的時候可以從領域?qū)拥臉I(yè)務邏輯開始,分析完領域?qū)雍笥缮瞄L數(shù)據(jù)庫的人員去設計數(shù)據(jù)庫表和存取方法,只要最后按照領域?qū)有枨筇峁┫鄳牟僮骷纯?#xff0c;領域?qū)佣x和實現(xiàn)Repository接口,基于接口完成業(yè)務邏輯的編寫,應用層調(diào)用領域?qū)雍突A層完成程序的功能和交互,開發(fā)界面的只要在需要數(shù)據(jù)的地方綁定數(shù)據(jù),在需要執(zhí)行命令的地方綁定命令就可以了。
第一次寫這么長的文章,感覺想說的東西很多,不知道該怎么寫,寫作能力太差啊。寫了3個多小時感覺亂七八糟的也不知道有沒有說明白,好吧,反正我自己是越寫越明白了。最后還有一點感受就是雖然紅軸比較輕,打字時間長了手也會酸啊。
0x08 相關下載
https://github.com/durow/TestArea/tree/master/UWPDDD
示例還沒有寫完,不過大概框架有了。還需要邊學習邊完善。
?
更多內(nèi)容歡迎訪問我的博客:http://durow.vip
轉載于:https://www.cnblogs.com/durow/p/4922964.html
總結
以上是生活随笔為你收集整理的领域驱动和MVVM应用于UWP开发的一些思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言 - 快速排序算法
- 下一篇: AX宏Macros运算