在上一講中,我們已經新建了一個聚合根物件Account,並已經可以開始設計領域模型了。在這一講中,我們會著重介紹EasyMemo領域模型的分析和設計,並引入Visual Studio Ultimate(旗艦版)版本的特性,介紹在Visual Studio 2013 Ultimate中如何使用體系結構建模工具進行領域模型設計,並自動化產生支援Apworks框架的程式碼。
界定上下文
由於EasyMemo所需實現的功能非常簡單,因此,我們很容易從領域概念中剝離出兩個界定上下文:使用者賬戶上下文和使用者便籤上下文。前者主要描述與使用者賬戶相關的領域模型,比如賬戶、角色、許可權、授權等;而後者則更側重於與便籤相關的部分。在實踐中,由於EasyMemo的業務非常簡單,我們並沒有必要對它進行嚴格的上下文區分,在此引入界定上下文區分,主要是為了之後對Visual Studio 2013 Ultimate體系結構工具的介紹進行鋪墊。
使用者賬戶領域模型
使用者賬戶領域模型包含了與賬戶及其許可權相關的概念和物件,以及它們之間的關係和行為。在EasyMemo中,我們會採用基於角色的訪問控制(Role Based Access Control,RBAC),相信這也是比較主流的一種許可權認證和授權方式。當然,它有一些弊端,比如對於資源的訪問授權(Privilege)型別不容易擴充套件,在一般的應用系統中,更偏向於將訪問授權型別固定下來。比如:某個應用僅提供“系統管理”、“賬戶新增”、“賬戶管理”等有限個授權型別,而且在整個程式執行過程中,這些授權型別是不能動態改變的。需要增加新的授權型別,則需要改變原始碼並重新生成,或者稍微好一點,修改資料庫中的固定值。不管怎樣,RBAC是好是不好,這裡就不多糾結了,在本文案例中,我們還是嘗試著實現一個簡單的RBAC模型吧。
使用者賬戶領域模型中最顯著的一個領域物件就是【賬戶(Account)】,在上文中,我們已經向EasyMemo.Domain類庫中新增了這個物件。既然是基於角色的訪問控制,那麼【角色(Role)】也是一個領域物件,或者更確切地說,是一個聚合根。現在的問題是,【賬戶】聚合與【角色】聚合的關係是什麼?
這就要看我們是如何理解這個問題的,如果【賬戶】的存在必須依賴於【角色】(也就是不存在一個不屬於任何角色的賬戶),那麼【角色】聚合就可以引用【賬戶】聚合,因為後者的生命週期比前者短(如果角色聚合生命週期結束,那麼賬戶也就沒有存在的意義);反之,【角色】的存在需要依賴【賬戶】嗎?顯然多數情形下不是。或許,兩者互不依賴的關係更為合理,不過此時需要引入另一個領域概念,表述【賬戶】與【角色】之間的“從屬”關聯。這種關聯可以是實體,也可以是值物件,它的生命週期依賴於其所在的聚合。本文所設計的使用者賬戶領域模型暫不會引入這樣的關聯實體,這也是為了讓事情變得更為簡單。不管如何,【賬戶】與【角色】之間的這種邏輯從屬關係,應該是容易被人理解的。
接下來的問題就是如何針對【角色】設定許可權。基本上這類需求可以通過“某種角色針對某種授權型別具有某種控制能力”一句話概括。那麼如何理解這句話?
- 某種角色:也就是在領域模型中【角色】的概念
- 某種授權型別:表示在領域模型中針對資源操作的一種分類。比如,“系統管理”、“賬戶新增”、“釋出文章”等,都屬於授權型別的一種。原則上,授權型別是可以組合的,比如具有“系統管理”授權的訪問者,當然可以訪問由“賬戶新增”授權標記的資源
- 某種控制能力:也就是角色對授權型別的許可權,最簡單的就是“允許”和“拒絕”兩種
把上面的理解串聯起來,我們就可以得到類似“(系統管理員)針對被(釋出文章)標記的資源,具有(允許)的訪問許可權”這樣的描述。於是,如果某個賬戶具有系統管理員身份,那麼它就可以釋出文章。這部分概念可以簡單地使用以下模型進行表述:
在下面的章節中,我們會使用Visual Studio 2013 Ultimate(旗艦版)的建模功能來完善這個模型。
使用者便籤領域模型
使用者便籤領域模型就非常簡單了,我們打算只包含一個【便籤】的物件,它定義了便籤的基本屬性和一些簡單的業務邏輯,在此就不多作說明了,在後續的實踐中再慢慢引入吧。
使用Visual Studio 2013 Ultimate(旗艦版)進行領域模型設計與自動化程式碼生成
首先宣告一點,本節內容需要依賴於Visual Studio 2013 Ultimate(旗艦版),其它版本的Visual Studio 2013將無法完成本節所演示的內容。如果您所使用的VS2013不是旗艦版,您可以跳過本節內容的閱讀,當然,也可以繼續閱讀,以瞭解旗艦版所提供的體系結構工具的使用方法。
現在,領域特定語言是一種潮流,它也是領域驅動設計所支援的一種用於交流的通用語言。通過工具進行模型設計並自動化產生程式碼,不僅可以更方便地以圖形化的方式與團隊進行架構設計討論,而且還可以加快開發速度,大大降低出錯機率。接下來,就讓我們一起學習,看看如何在Visual Studio 2013 Ultimate(旗艦版)中,結合Apworks的建模外掛,實現領域模型的設計與自動化程式碼生成。
先決條件
要對本節所討論的內容進行演練,在開始前,您需要確認您的系統是否滿足以下先決條件:
- 安裝Visual Studio 2013 Ultimate(旗艦版)
- 安裝Apworks建模外掛:請【單擊此處】下載Apworks建模外掛。在解開的壓縮包中,有VS2013和VS2015兩個版本,強烈建議使用VS2013,因為在VS2015的自動化程式碼產生中,存在一些缺陷,目前無法正常生成程式碼。這是Visual Studio 2015的問題,目前未決
- 下載Apworks定製的型別生成模板T4檔案,並儲存在本地備用:請【單擊此處】下載
Apworks建模外掛包含了對Visual Studio體系結構工具UML語言的擴充套件,增加了兩個Stereotype,分別是aggregate root和entity。在自動化程式碼生成時,T4引擎會根據不同的stereotype標註來決定產生不同的程式碼結構。
新建建模專案
在EasyMemo解決方案上單擊滑鼠右鍵,選擇【新增】->【新建專案】選單。在彈出的【新增新專案】對話方塊中,選擇【建模專案】,取名為EasyMemo.Design,然後單擊【確定】按鈕:
環境設定與配置
開啟【UML模型資源管理器】,在EasyMemo.Design模型上單擊滑鼠右鍵,選擇【屬性】:
在【屬性】工具窗中,展開【通用】節點,在【Profiles】專案上開啟下拉選單,然後勾選【C# Profile】和【Apworks Entity Profile】兩項:
在Visual Studio IDE的主選單上,點選【體系結構】-> 【新建關係圖】選單,此時會開啟【新增新關係圖】對話方塊。
在【新增新關係圖】對話方塊中,選擇【UML類圖】,在【名稱】一欄輸入Model.classdiagram,【新增到建模專案】一欄選擇EasyMemo.Design,然後點選【確定】按鈕:
此時,Visual Studio會自動開啟Model類圖的設計介面,供使用者對類圖進行設計。在此之前,我們仍然需要對環境進行配置,以便能夠在接下來的步驟中正確地產生程式碼。首先,在檔案系統中開啟EasyMemo.Design專案所在的目錄,然後將已經下載好的Apworks定製的型別生成T4模板解壓到Templates子目錄下:
為了今後的操作方便,強烈建議在【解決方案資源管理器】中,在EasyMemo.Design專案上顯示所有檔案,並把這個Templates資料夾【包括在專案中】:
在Visual Studio IDE的主選單上,點選【體系結構】 –> 【配置預設程式碼生成設定】選單,開啟【文字模板繫結】對話方塊:
在【文字模板繫結】對話方塊中,依次針對ClassTemplate、EnumTemplate、InterfaceTemplate以及StructTemplate進行設定:
- 【模板檔案路徑(*.t4)】選擇EasyMemo.Design檔案系統目錄下Templates資料夾中的相應檔案,例如ClassTemplate.t4、EnumTemplate.t4、InterfaceTemplate.t4以及StructTemplate.t4
- 我們打算將C#程式碼生成到EasyMemo.Domain專案的Model資料夾下,因此,在【目標檔案目錄】中直接輸入【Model】,在【專案路徑】中設定EasyMemo.Domain.csproj專案
單擊【確定】按鈕關閉對話方塊。
開始使用
OK,現在我們就可以開始使用類圖設計器設計我們的領域模型了。開啟【工具箱】,選擇【類】工具,在類圖設計器上新增一個類,並改名為AggregateRoot:
點選該類,在【屬性】頁中,在【Stereotypes】下拉選單中,勾選【C# class】和【aggregate root】兩個stereotypes:
繼續展開【Stereotypes】節點下的【C# class】節點,設定【Is Partial】屬性為True,並在【繼承】分組中,設定【Is Abstract】屬性為True:
在AggregateRoot類上單擊滑鼠右鍵,選擇【新增】->【特性】選單項,新增一個名稱為ID、型別為Guid的特性,並以同樣的方式新增一個名稱為IsDeleted,型別為Nullable<bool>的特性。在新增完這兩個特性後,AggregateRoot類如下:
現在,讓我們嘗試程式碼生成。在類圖設計器的空白區域單擊滑鼠右鍵,選擇【生成程式碼】:
在【程式碼生成】進度視窗完成操作後,你會發現,在我們的EasyMemo.Domain專案下,多了一個Model的目錄,在這個目錄下,生成了AggregateRoot.cs檔案:
雙擊開啟這個檔案,可以看到生成的原始碼如下:
可以關注以下幾點:
- AggregateRoot類是抽象類,因為之前我們設定了【Is Abstract】屬性為True
- AggregateRoot類是部分類,因為之前我們在【C# class】stereotype中設定了【Is Partial】屬性為True
- AggregateRoot類實現了Apworks.IAggregateRoot介面,因為我們對其應用了【aggregate root】stereotype
- 所有的特性(屬性)都是虛實現(virtual),因為每個特性的【Is Leaf】屬性值預設都是False:這在接下來使用Entity Framework的延遲載入特性會很有幫助。當然,據說Entity Framework 7已經取消了延遲載入功能
您或許會發現,這個類怎麼沒有包含的名稱空間?不錯,要設定生成程式碼的名稱空間,您還需要對類圖做一些改動:
- 開啟類圖設計器,在【工具箱】中,找到【包】工具,往類圖設計器中新增一個【包】
- 選中Package 1包,在【屬性】的【Stereotypes】下拉選單中,勾選【C# namespace】
- 將Package 1包的【Name】屬性設定為EasyMemo.Domain.Model
- 將AggregateRoot類拖入EasyMemo.Domain.Model包,此時我們的類圖如下:
OK,再次生成程式碼,可以看到,生成的程式碼中已經包含了名稱空間定義了:
對界定上下文的支援
Visual Studio 2013 Ultimate的體系結構建模系統是以模型為單位的,也就是說,即使你向你的建模專案中新增了多個類圖,這些類圖也都是公用一個模型。例如,我們可以在EasyMemo.Design專案中再新建一個名稱為Accounts的類圖,表示使用者賬戶領域模型,然後在這個類圖中以上述類似的方式將Account類設計出來:
由於Account類本身應該繼承於AggregateRoot類,因此,我們可以直接從【UML模型資源管理器】中,找到AggregateRoot類的定義,然後用拖拽的方式新增到Accounts類設計器中
從【UML工具箱】中選中【繼承】工具,然後用滑鼠從Account類拖到AggregateRoot類,表示前者從後者繼承。在設定了這一類關係後,我們的類圖如下:
OK,再次生成程式碼,可以看到,新出現的Account.cs檔案內容如下:
完成我們的領域模型
至此,我們已經能夠在Visual Studio 2013 Ultimate中使用體系結構和建模工具來圖形化設計領域模型,以及自動化程式碼的生成了。現在,就讓我們一起完成EasyMemo的領域模型吧。
貧血模型???
是的,通過類圖設計器設計的領域模型不包含任何方法定義。事實上也沒辦法在類圖上編寫類中各方法的原始碼。還記得之前我們在【C# class】這一stereotype上設定【Is Partial】為True嗎?這就使得我們有辦法在已有的型別上在不改變自動生成的程式碼的基礎上,加入我們自己的業務邏輯:只需使用C#中部分類的特性即可!
總結
本文首先簡單介紹了EasyMemo的界定上下文以及領域模型,並詳細介紹了在Visual Studio 2013 Ultimate(旗艦版)中使用體系結構建模工具和Apworks的建模擴充套件進行領域模型的設計,並實現程式碼自動化生成。下一講我將重點介紹基於Entity Framework的倉儲實現。
原始碼下載
請【單擊此處】下載截止到本文時EasyMemo的原始碼。如果您的Visual Studio 2013不是旗艦版,您也可以正常開啟EasyMemo.sln解決方案,但無法開啟EasyMemo.Design專案。但這並不會對你使用整個解決方案帶來不便,您只需將EasyMemo.Design專案從解決方案中移除出去就可以了。