当前位置: 首页 > 编程学习 > 软件工程 > 领域驱动 > 正文

从零开始使用CodeArt实践最佳领域驱动开发(四)

2018-04-22 来源:博客园/一个人抽烟

6.为领域模型Permission编码

现在我们为账户子系统(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的程序集: