在上一講中,我們已經新建了一個聚合根對象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項目從解決方案中移除出去就可以了。