Apworks框架实战(五):EasyMemo的领域模型设计

在上一讲中,我们已经新建了一个聚合根对象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)】也是一个领域对象,或者更确切地说,是一个聚合根。现在的问题是,【账户】聚合与【角色】聚合的关系是什么?

这就要看我们是如何理解这个问题的,如果【账户】的存在必须依赖于【角色】(也就是不存在一个不属于任何角色的账户),那么【角色】聚合就可以引用【账户】聚合,因为后者的生命周期比前者短(如果角色聚合生命周期结束,那么账户也就没有存在的意义);反之,【角色】的存在需要依赖【账户】吗?显然多数情形下不是。或许,两者互不依赖的关系更为合理,不过此时需要引入另一个领域概念,表述【账户】与【角色】之间的“从属”关联。这种关联可以是实体,也可以是值对象,它的生命周期依赖于其所在的聚合。本文所设计的用户账户领域模型暂不会引入这样的关联实体,这也是为了让事情变得更为简单。不管如何,【账户】与【角色】之间的这种逻辑从属关系,应该是容易被人理解的。

接下来的问题就是如何针对【角色】设定权限。基本上这类需求可以通过“某种角色针对某种授权类型具有某种控制能力”一句话概括。那么如何理解这句话?

  1. 某种角色:也就是在领域模型中【角色】的概念
  2. 某种授权类型:表示在领域模型中针对资源操作的一种分类。比如,“系统管理”、“账户添加”、“发布文章”等,都属于授权类型的一种。原则上,授权类型是可以组合的,比如具有“系统管理”授权的访问者,当然可以访问由“账户添加”授权标记的资源
  3. 某种控制能力:也就是角色对授权类型的权限,最简单的就是“允许”和“拒绝”两种

把上面的理解串联起来,我们就可以得到类似“(系统管理员)针对被(发布文章)标记的资源,具有(允许)的访问权限”这样的描述。于是,如果某个账户具有系统管理员身份,那么它就可以发布文章。这部分概念可以简单地使用以下模型进行表述:

image

在下面的章节中,我们会使用Visual Studio 2013 Ultimate(旗舰版)的建模功能来完善这个模型。

用户便签领域模型

用户便签领域模型就非常简单了,我们打算只包含一个【便签】的对象,它定义了便签的基本属性和一些简单的业务逻辑,在此就不多作说明了,在后续的实践中再慢慢引入吧。

使用Visual Studio 2013 Ultimate(旗舰版)进行领域模型设计与自动化代码生成

首先声明一点,本节内容需要依赖于Visual Studio 2013 Ultimate(旗舰版),其它版本的Visual Studio 2013将无法完成本节所演示的内容。如果您所使用的VS2013不是旗舰版,您可以跳过本节内容的阅读,当然,也可以继续阅读,以了解旗舰版所提供的体系结构工具的使用方法。

现在,领域特定语言是一种潮流,它也是领域驱动设计所支持的一种用于交流的通用语言。通过工具进行模型设计并自动化产生代码,不仅可以更方便地以图形化的方式与团队进行架构设计讨论,而且还可以加快开发速度,大大降低出错几率。接下来,就让我们一起学习,看看如何在Visual Studio 2013 Ultimate(旗舰版)中,结合Apworks的建模插件,实现领域模型的设计与自动化代码生成。

先决条件

要对本节所讨论的内容进行演练,在开始前,您需要确认您的系统是否满足以下先决条件:

  1. 安装Visual Studio 2013 Ultimate(旗舰版)
  2. 安装Apworks建模插件:请【单击此处】下载Apworks建模插件。在解开的压缩包中,有VS2013和VS2015两个版本,强烈建议使用VS2013,因为在VS2015的自动化代码产生中,存在一些缺陷,目前无法正常生成代码。这是Visual Studio 2015的问题,目前未决
  3. 下载Apworks定制的类型生成模板T4文件,并保存在本地备用:请【单击此处】下载

Apworks建模插件包含了对Visual Studio体系结构工具UML语言的扩展,增加了两个Stereotype,分别是aggregate root和entity。在自动化代码生成时,T4引擎会根据不同的stereotype标注来决定产生不同的代码结构。

新建建模项目

在EasyMemo解决方案上单击鼠标右键,选择【添加】->【新建项目】菜单。在弹出的【添加新项目】对话框中,选择【建模项目】,取名为EasyMemo.Design,然后单击【确定】按钮:

image

环境设定与配置

打开【UML模型资源管理器】,在EasyMemo.Design模型上单击鼠标右键,选择【属性】:

image

在【属性】工具窗中,展开【通用】节点,在【Profiles】项目上打开下拉列表,然后勾选【C# Profile】和【Apworks Entity Profile】两项:

image

在Visual Studio IDE的主菜单上,点击【体系结构】-> 【新建关系图】菜单,此时会打开【添加新关系图】对话框。

image

在【添加新关系图】对话框中,选择【UML类图】,在【名称】一栏输入Model.classdiagram,【添加到建模项目】一栏选择EasyMemo.Design,然后点击【确定】按钮:

image

此时,Visual Studio会自动打开Model类图的设计界面,供用户对类图进行设计。在此之前,我们仍然需要对环境进行配置,以便能够在接下来的步骤中正确地产生代码。首先,在文件系统中打开EasyMemo.Design项目所在的目录,然后将已经下载好的Apworks定制的类型生成T4模板解压到Templates子目录下:

image

为了今后的操作方便,强烈建议在【解决方案资源管理器】中,在EasyMemo.Design项目上显示所有文件,并把这个Templates文件夹【包括在项目中】:

image

在Visual Studio IDE的主菜单上,点击【体系结构】 –> 【配置默认代码生成设置】菜单,打开【文本模板绑定】对话框:

image

在【文本模板绑定】对话框中,依次针对ClassTemplate、EnumTemplate、InterfaceTemplate以及StructTemplate进行设置:

  1. 【模板文件路径(*.t4)】选择EasyMemo.Design文件系统目录下Templates文件夹中的相应文件,例如ClassTemplate.t4、EnumTemplate.t4、InterfaceTemplate.t4以及StructTemplate.t4
  2. 我们打算将C#代码生成到EasyMemo.Domain项目的Model文件夹下,因此,在【目标文件目录】中直接输入【Model】,在【项目路径】中设置EasyMemo.Domain.csproj项目

image

单击【确定】按钮关闭对话框。

开始使用

OK,现在我们就可以开始使用类图设计器设计我们的领域模型了。打开【工具箱】,选择【类】工具,在类图设计器上添加一个类,并改名为AggregateRoot:

image

点击该类,在【属性】页中,在【Stereotypes】下拉列表中,勾选【C# class】和【aggregate root】两个stereotypes:

image

继续展开【Stereotypes】节点下的【C# class】节点,设置【Is Partial】属性为True,并在【继承】分组中,设置【Is Abstract】属性为True:

image

在AggregateRoot类上单击鼠标右键,选择【添加】->【特性】菜单项,添加一个名称为ID、类型为Guid的特性,并以同样的方式添加一个名称为IsDeleted,类型为Nullable<bool>的特性。在添加完这两个特性后,AggregateRoot类如下:

image

现在,让我们尝试代码生成。在类图设计器的空白区域单击鼠标右键,选择【生成代码】:

image

在【代码生成】进度窗口完成操作后,你会发现,在我们的EasyMemo.Domain项目下,多了一个Model的目录,在这个目录下,生成了AggregateRoot.cs文件:

image

双击打开这个文件,可以看到生成的源代码如下:

image

可以关注以下几点:

  1. AggregateRoot类是抽象类,因为之前我们设置了【Is Abstract】属性为True
  2. AggregateRoot类是部分类,因为之前我们在【C# class】stereotype中设置了【Is Partial】属性为True
  3. AggregateRoot类实现了Apworks.IAggregateRoot接口,因为我们对其应用了【aggregate root】stereotype
  4. 所有的特性(属性)都是虚实现(virtual),因为每个特性的【Is Leaf】属性值默认都是False:这在接下来使用Entity Framework的延迟加载特性会很有帮助。当然,据说Entity Framework 7已经取消了延迟加载功能

您或许会发现,这个类怎么没有包含的命名空间?不错,要设定生成代码的命名空间,您还需要对类图做一些改动:

  1. 打开类图设计器,在【工具箱】中,找到【包】工具,往类图设计器中添加一个【包】
  2. 选中Package 1包,在【属性】的【Stereotypes】下拉列表中,勾选【C# namespace】

    image
  3. 将Package 1包的【Name】属性设置为EasyMemo.Domain.Model
  4. 将AggregateRoot类拖入EasyMemo.Domain.Model包,此时我们的类图如下:

    image

OK,再次生成代码,可以看到,生成的代码中已经包含了命名空间定义了:

image

对界定上下文的支持

Visual Studio 2013 Ultimate的体系结构建模系统是以模型为单位的,也就是说,即使你向你的建模项目中添加了多个类图,这些类图也都是公用一个模型。例如,我们可以在EasyMemo.Design项目中再新建一个名称为Accounts的类图,表示用户账户领域模型,然后在这个类图中以上述类似的方式将Account类设计出来:

image

由于Account类本身应该继承于AggregateRoot类,因此,我们可以直接从【UML模型资源管理器】中,找到AggregateRoot类的定义,然后用拖拽的方式添加到Accounts类设计器中

image

从【UML工具箱】中选中【继承】工具,然后用鼠标从Account类拖到AggregateRoot类,表示前者从后者继承。在设定了这一类关系后,我们的类图如下:

image

OK,再次生成代码,可以看到,新出现的Account.cs文件内容如下:

image

完成我们的领域模型

至此,我们已经能够在Visual Studio 2013 Ultimate中使用体系结构和建模工具来图形化设计领域模型,以及自动化代码的生成了。现在,就让我们一起完成EasyMemo的领域模型吧。

image

贫血模型???

是的,通过类图设计器设计的领域模型不包含任何方法定义。事实上也没办法在类图上编写类中各方法的源代码。还记得之前我们在【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项目从解决方案中移除出去就可以了。

posted @ 2015-10-03 20:54  dax.net  阅读(...)  评论(...编辑  收藏