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的程序集:

CodeArt入门教程(四)

   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入门教程(四)

   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的代码如下: 

CodeArt入门教程(四)

我们稍后会结合属性规则验证详细讲解PermissionSpecification里代码的含义,现在请将思路放回到Permission代码段里。

5)public class Permission : AggregateRoot<Permission, Guid>,这段代码定义了Permission类,该类继承自AggregateRoot<Permission, Guid>,这是一个泛型基类,第一个泛型参数传入Permission类型即可,第二个泛型参数表示Permission这个聚合根的标识符类型,我们在这里定义为Guid。由于聚合根也是实体对象,所以必须为聚合根指定标识符的类型。另外,使用CA做开发,聚合根都需要继承自AggregateRoot<TRoot, TIdentity>基类,它实现了多项有关聚合根的技术细节,大家不要自己去实现IAggreateRoot接口。

 本章剩余内容正在整理上传中。。。

相关推荐