CodeArt入门教程(四)
6.为领域模型编码
现在我们为账户子系统(AccountSubsystem)设计领域对象并编码实现细节。
账号、角色、权限是账户子系统里已知的3个事物,而一个子系统里面可以有多个内聚模型,所以我们首先要思考的问题是:以谁为聚合根创建第一个内聚模型?
与划分子系统的思路一样,我们以最简单、最独立的事物作为突破口。简单是指事物在特定领域里的特征比较少,没有那么复杂。很明显,权限是最简单、最独立的,它不依赖于账号、角色而独立存在,而且从目前收集到的需求来看,权限的特征只需要有名称即可。所以我们尝试以权限(Permission)为聚合根创建第一个内聚模型。请各位注意,我在这里用“尝试”一词表达要做的工作,因为我们并不能保证当前做的决策100%是对的,但是勇敢的去尝试总比畏首畏尾、不敢迈出第一个步子、始终原地踏步要好的多。所以各位在实践的时候,如果有了灵感、有了大致的思路,就算思路还不够全面、不够清晰,你也可以大胆的去尝试,CA可以保证即便设计有误也能及时修正。使用CA开发项目的过程就是不断的在分析、设计、实践、修正中反复迭代的过程,最终你会提炼出与事物本质特征相符的领域模型。
在考虑将Permission作为聚合根后,我们依然要对这一决策提出质疑,要反问自己Permission是值对象还是实体对象。如果Permission是值对象,那么它就不能作为聚合根了,因为聚合根必须是实体对象。使用CA做开发,我们要善于使用这种思考技巧:先根据脑海的“嗅觉”做出设计上的判断,再反问自己各类问题以便验证或推翻这项判断。这种先做决策再试图推翻的思考方式会带给你意想不到的惊喜,如果你推翻不了它,证明所做的决策就是对的,反之就需要改进这项决策,然后再去想办法推翻新的决策,一直到你找到无法推翻的决策为止。
判断Permission是否为实体对象的依据之一就是外部事物是否需要直接找到它。这里的外部事物是指"应用层"和"领域模型层里除Permission以外的领域对象"。首先,要判断角色是否拥有某项权限,我们肯定需要建立角色和权限的引用关系,由此可以推断出,权限应该是需要被外部对象角色所直接引用的(注意,由于角色这一事物还没有开始设计,所以这里我们只是做的假设,辅助我们判断Permission的设计)。另外,权限的名称、描述等信息需要由系统的使用者去直接填写或更改,所以我们可以想象得到,应用层需要根据标识符获取Permission对象,将其读取后呈现相关信息给系统使用者查看(注意,我们这里是借助UI操作的方式来辅助我们判断Permission是否为实体对象,再次声明,领域模型的建立不仅仅是为了满足UI操作,但是正确的领域模型一定可以完全满足UI操作,因此,借助它来帮助我们分析领域对象如何设计是可以的,只是注意要适度,不要局限于某一种UI操作来设计对象。)。所以我们判定Permission是实体对象,它具有成为聚合根的基本条件。
然后我们再思考,Permission是聚合根还是内聚成员?很明显,Permission只能是聚合根,因为我们还无法从权限事物里找出第二个相关的事物,Permission只能作为聚合根存在。至此,对Permission的初步分析工作就完成了,下面贴出Permission的全部代码并作出详细说明:
1 using System; 2 3 using CodeArt.DomainDriven; 4 5 namespace AccountSubsystem 6 { 7 /// <summary> 8 /// 权限对象 9 /// </summary> 10 [ObjectRepository(typeof(IPermissionRepository))] 11 [ObjectValidator(typeof(PermissionSpecification))] 12 public class Permission : AggregateRoot<Permission, Guid> 13 { 14 internal static readonly DomainProperty NameProperty = DomainProperty.Register<string, Permission>("Name"); 15 16 /// <summary> 17 /// 权限名称 18 /// </summary> 19 [PropertyRepository()] 20 [NotEmpty()] 21 [StringLength(2, 25)] 22 public string Name 23 { 24 get 25 { 26 return GetValue<string>(NameProperty); 27 } 28 set 29 { 30 SetValue(NameProperty, value); 31 } 32 } 33 34 35 internal static readonly DomainProperty MarkedCodeProperty = DomainProperty.Register<string, Permission>("MarkedCode"); 36 37 38 /// <summary> 39 /// <para>权限的唯一标示,可以由用户设置</para> 40 /// <para>可以通过唯一标示找到权限对象</para> 41 /// <para>该属性可以为空</para> 42 /// </summary> 43 [PropertyRepository()] 44 [StringLength(0, 50)] 45 public string MarkedCode 46 { 47 get 48 { 49 return GetValue<string>(MarkedCodeProperty); 50 } 51 set 52 { 53 SetValue(MarkedCodeProperty, value); 54 } 55 } 56 57 /// <summary> 58 /// 是否定义了标识码 59 /// </summary> 60 public bool DeclareMarkedCode 61 { 62 get 63 { 64 return !string.IsNullOrEmpty(this.MarkedCode); 65 } 66 } 67 68 69 private static readonly DomainProperty DescriptionProperty = DomainProperty.Register<string, Permission>("Description"); 70 71 /// <summary> 72 /// <para>描述</para> 73 /// </summary> 74 [PropertyRepository()] 75 [StringLength(0, 200)] 76 public string Description 77 { 78 get 79 { 80 return GetValue<string>(DescriptionProperty); 81 } 82 set 83 { 84 SetValue(DescriptionProperty, value); 85 } 86 } 87 88 [ConstructorRepository()] 89 public Permission(Guid id) 90 : base(id) 91 { 92 this.OnConstructed(); 93 } 94 95 #region 空对象 96 97 private class PermissionEmpty : Permission 98 { 99 public PermissionEmpty() 100 : base(Guid.Empty) 101 { 102 this.OnConstructed(); 103 } 104 105 public override bool IsEmpty() 106 { 107 return true; 108 } 109 } 110 111 public static readonly Permission Empty = new PermissionEmpty(); 112 113 #endregion 114 } 115 }
1)using CodeArt.DomainDriven; 表示引入CodeArt.DomainDriven命名空间,该命名空间提供了领域设计的技术支持。要使用该命名空间你需要在账户子系统中引用CodeArt.DomainDriven的程序集:
2)namespace AccountSubsystem 表示Permission对象处于账户子系统内。请注意子系统的命名约定:在子系统的实际名称上追加Subsystem后缀组成。例如:UserSubsystem(用户子系统)、CarSubsystem(车辆子系统)。
3)我们在Permission的类定义里标记了特性标签 [ObjectRepository(typeof(IPermissionRepository))] 指示对象是可以被仓储的,并且Permission的仓储接口是IPermissionRepository。但是请大家一定注意,我们已经决定了Permission是根对象,因此这个对象继承自AggregateRoot<Permission, Guid>(这段代码后文会有详细说明),所以就算Permission没有标记ObjectRepository特性,只要Permission继承了AggregateRoot<Permission, Guid>这个基类,就表示Permission是聚合根,那么它就是一定可以被仓储保存的。那么这个特性的意义何在?意义在于提高开发效率,缩短开发时间。只要当你对聚合根标记了ObjectRepository,那么你就可以使用CA内置的ORM工具,自动化存储Permission,你不需要写一行代码就可以实现保存Permission,甚至连表都不需要设计,CA的内置模块会帮你搞定这一切。要使用ObjectRepository特性请引用程序集CodeArt.DomainDriven.DataAccess:
CodeArt.DomainDriven.DataAccess是CodeArt 3.0提供的新组件。与使用CA 2.0版本相比,程序员的工作量降低了50%。当然,你也可以不使用CA提供的ORM特性,自行编码如何存储对象,这点后文会有介绍。但是强烈建议你使用这一特性,随着CA的发展,我们会逐步提升DataAccess的各项指标,你的项目同步更新CA新版本就可以享受我们的工作成果。
4)紧接着我们为Permission类又标记了[ObjectValidator(typeof(PermissionSpecification))]。正如其名,ObjectValidator表示对象验证器,还记得我们在前文里说过“每个领域对象都具有验证固定规则的能力”这个领域规则吗?ObjectValidator就是用于对象验证的,为对象标记这个特性并且传入参数PermissionSpecification,就表示Permission对象需要满足类型名称为PermissionSpecification的规格。在PermissionSpecification的代码里,我们会编码定义规格的细节。CA强调每个对象都应该满足1个或者多个需要满足的规格,所以你可以传入多个规格类型给ObjectValidator特性。当对象被提交给仓储的时候,这些规格会被自动验证。PermissionSpecification的代码如下:
我们稍后会结合属性规则验证详细讲解PermissionSpecification里代码的含义,现在请将思路放回到Permission代码段里。
5)public class Permission : AggregateRoot<Permission, Guid>,这段代码定义了Permission类,该类继承自AggregateRoot<Permission, Guid>,这是一个泛型基类,第一个泛型参数传入Permission类型即可,第二个泛型参数表示Permission这个聚合根的标识符类型,我们在这里定义为Guid。由于聚合根也是实体对象,所以必须为聚合根指定标识符的类型。另外,使用CA做开发,聚合根都需要继承自AggregateRoot<TRoot, TIdentity>基类,它实现了多项有关聚合根的技术细节,大家不要自己去实现IAggreateRoot接口。
本章剩余内容正在整理上传中。。。