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

internal static readonly DomainProperty MarkedCodeProperty = DomainProperty.Register<string, Permission>("MarkedCode");

/// <summary>
/// <para>权限的唯一标示,可以由用户设置</para>
/// <para>可以通过唯一标示找到权限对象</para>
/// <para>该属性可以为空</para>
/// </summary>
[PropertyRepository()]
[StringLength(0, 50)]
public string MarkedCode
{
    get
    {
        return GetValue<string>(MarkedCodeProperty);
    }
    set
    {
        SetValue(MarkedCodeProperty, value);
    }
}

/// <summary>
/// 是否定义了标识码
/// </summary>
public bool DeclareMarkedCode
{
    get
    {
        return !string.IsNullOrEmpty(this.MarkedCode);
    }
}

   DomainProperty NameProperty
是圈子属性定义的扬言,DomainProperty是世界属性定义的档次,所有世界属性定义都应该选用那些序列。请小心领域属性定义名称NameProperty,CA规定具备的小圈子属性定义必须在实际的特性名后追加Porperty,也就是XXXProperty的格式表示XXX领域属性的概念,那是使用CA做开发须要遵从的标准之一。

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

  大家就此用世界驱动设计就是为着寻找事物在一定领域里的本质特征,为了兑现这一对象,我们会基于领域的考虑去考虑问题,思考的结果之一就是寻觅到了事物本来的平整,这么些稳定规则就是描述事物本质特征的一个地点。不过数据库字段设计是基于表的规划,设计的表有可能是某个业务须求的表,也有可能是中间表,甚至临时表,它们被规划的目标就是为了方便存储数据照旧为某个业务处理做多少上的扶助。请留心,数据库里的表为某个业务的落到实处做多少上的帮衬,不意味数据表自身处理了事情,事实上,开发人士还索要编制大批量操作数据库的代码来促成工作逻辑。与之相反的是,领域对象自然就颇具处理复杂工作的能力,它不是数码的提供者而是业务的处理者,那是两者极其本质的分别。

  5)public class Permission :
AggregateRoot<Permission,
Guid>,那段代码定义了Permission类,该类继承自AggregateRoot<Permission,
Guid>,那是一个泛型基类,第三个泛型参数传入Permission类型即可,首个泛型参数表示Permission这么些聚合根的标识符类型,我们在这里定义为Guid。由于聚合根也是实业对象,所以必须为聚合根指定标识符的档次。此外,使用CA做开发,聚合根都亟需延续自AggregateRoot<TRoot,
TIdentity>基类,它达成了多项关于聚合根的技术细节,大家无需自己去落到实处IAggreateRoot接口。

   CodeArt.DomainDriven.DataAccess是CodeArt
3.0提供的新组件。与利用CA
2.0本子对照,程序员的工作量下降了50%。当然,你也足以不利用CA提供的ORM特性,自行编码怎么样存储对象,那点后文会有介绍。不过强烈提议你利用这一风味,随着CA的上扬,大家会日益升级DataAccess的各个目的,你的品种同步更新CA新本子就足以分享大家的行事成果。

  由此,当大家查询八个篇章对象的时候,由于只用加载文章内聚模型内部的数量所以性能比查询完整的小说音讯(包涵分类音讯在内的完全引用链)要好的多。此外,尽管大家在查询完小说对象后,又要运用它的归类属性,由于有对象缓存的来头,分类属性的值来自于缓存区而不是一贯读取数据库,由此,大家并无法武断的认为三回性执行sql查询所有的情节就比二次访问的特性要高,性能的优化要基于环境上下文综合性的判断,找出性能的瓶颈再去优化。

  那里的NotEmpty和StringLength特性,都是以前涉嫌的定位规则的一种突显,在CA里,你可以为目的标记ObjectValidator特性并为这些特点传入多项原则标准(达成IObjectValidator的接口就足以成为标准标准)来验证目的级其他合法性,也得以对天地属性直接以标记特性的章程定义属性要求满足的规则。为属性打特性落成属性验证这一点并不是CA特有的艺术,许多别样框架也有相近的编制,由此不再过多表达。

  领域属性的概念一旦付出就不足更改,大家得以增加它的职务但无法抹去它的留存(改变属性定义的对准也终究抹去从前属性定义的存在)。因为东西的本质特征是不会被抹去的(
比如说,一个学员的年龄明天会有,难道前些天就丢掉了?)。当然,也有可能鉴于大家统筹上的错误导致了一个领域属性不应该存在,这时候你剔除该领域属性相关的代码就可以了,所以要是是安顿好了的圈子属性定义就自然是静态只读的。

[SafeAccess]
internal sealed class PermissionSpecification : ObjectValidator<Permission>
{
    public PermissionSpecification()
    {

    }

    protected override void Validate(Permission obj, ValidationResult result)
    {
        Validator.CheckPropertyRepeated(obj, Permission.NameProperty, result);
        Validator.CheckPropertyRepeated(obj, Permission.MarkedCodeProperty, result);
    }
}

  所以,问题应运而生在站点对权力的渴求是硬性的、是硬编码的,而权力对象的定义是保留在中远距离门户服务的存储里的,一个是硬编码,一个是储存里的靶子,他们互相没有映射关系。因而大家就统筹了“标记码”来彰显这种映射关系。你可以为每个权限对象设置一个马克edCode的属性值,那几个值同时也是站点硬性编码的值,将该值提交给门户服务,门户服务就可以通过该值找到唯一一个相应的权位对象。那就是大家为何要统筹马克edCode属性的来头。有了那项体制后,站点里可以调用类似那样的代码:ValidatePermission(“ViewEmployeeInfo”)来表示要求注脚当前登录者是或不是富有查看员工音信的权柄,而大家在开立名称为“查看员工音信”的权位的时候,可以指定马克edCode为“ViewEmployeeInfo”,那样映射关系就创设好了,由于标记码是我们硬编码的内需而创立的,所以它不会像权限名称那样有可能会改变,可以放心使用。那种以标识码的措施将系统的硬编码和对应的园地对象一一映射的体制也可以用来此外须要,不必局限于权力模块。

  所以,我们不要看不起那些看似不难却饱含深意的空对象,它是兑现CA全部政策的一个主要环节,对分离关怀点、切断对象重视性上有很大的拉扯。下降对象时期看重关系涉及到的话题相比较多,近期大家只谈谈到那里,可是各位要明白,CA提供的不仅仅是一个框架而是一个举办项目的总体政策,背后隐藏着一连串解决各项问题的想想理论。当你蒙受系统规划上的题材时,这几个理论像军师一样扶助你更好的做定夺。所以在我的教程里会用多量的篇幅钻探思想方面的话题。附带说一句,CA
3.0还落到实处了目标快照机制,提供了可以保存被去除对象的快照成效,你可以在系统中如故选取被删除了的指标,可是可以唤起类似那样的新闻:“该房源已被剔除,您看来的是快照新闻”,快照特性不但用于UI突显,在世界模型层也有很大的功效,后文再详尽介绍。

  我们试想一下,系统既然有权力机制,那么肯定会有证实的急需,比如在职工列表的页面(大家就以显示层是B/S站点为例表明)有个Page_Load方法,该方法里或者会阐明当前登录人是不是有翻动该员工音讯的权力。假使验证权限的表示代码是
ValidatePermission(“查看员工新闻”)。ValidatePermission是认证权限的办法,该方法是展现层定义的,与世界模型层无关,这一个方法会将近日登录人的数码和权限的称呼提交到山头服务,由门户服务判断结果。我们不用在意达成的细节,门户服务处理请求的建制接轨教程中会讲解。在那边各位请考虑一个问题,大家将权限的称号以硬编码的花样提交给门户服务,门户服务需求通过权限名称找到权限对象,然后调用权限对象的领域方法判断登录人是或不是合法,那么问题就来了,若是哪天由于有些缘故,咱们须求在系统后台更改那个权力的称号如何做?咱们把权限名称“查看员工音讯”修改为“查看多少个职工音讯”,那时候大家不得不找到员工列表页,手工改代码修改名称,然后编译代码,重新上传到服务器。可以设想得到,当权限数量多了那种爱惜卓殊麻烦。

  this.OnConstructed();代码很关键,表示构造对象的行事已总体做到。使用CA编写领域对象,当目的构造函数工作截止的时候,必须调用 OnConstructed
方法,那是各位要求遵循的应用口径。之所以有那项条件是因为近日还不曾技术平台(.NET、JAVA等)提供了目标被协会已毕的轩然大波给程序员使用。而CA须要监视各个领域事件,那包含世界对象被协会已毕的风云,这个事件会对世界规划带来很大的补益(后续教程会详述)。由此须求我们手动调用OnConstructed方法予以框架提醒对象社团已到位。在CA后续的版本里我们会设想扩充动态编译的机制来落到实处自动化处理,但在脚下版本中请咱们听从那个动用约定。

  然后大家再想想,Permission是聚合根如故内聚成员?很扎眼,Permission只好是聚合根,因为我们还不可以从权力事物里找出第一个相关的事物,Permission只好当做聚合根存在。至此,对Permission的上马分析工作就完结了,下边贴出Permission的前期代码并作出详尽表达:

  现在大家为账户子系统(AccountSubsystem)设计领域对象并编码已毕细节。

  这是大家率先个代码示例,目的在于让各位熟领域对象的为主写法。所以那边并没有关系到世界表现、对象引用关系、领域事件、移动领域对象等高档话题。

  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 }

  那为啥CA可以确保聚合根的分子数量很少呢?因为不管工作多么复杂,大家都可以将复杂的业务拆分成八个内聚模型,每个内聚模型仅负责1个关怀点,那样一个内聚模型里的聚合根和内聚成员的总和会相当少。每个聚合根会提供领域方法以便应用层调用。有时候也会油然则生多个聚合根共同落成某项职分的图景,不过那种“共同落成”指的是聚合根A调用聚合根B的格局,B的情势在B内部概念,聚合根A不会长远到以聚合根B内部去告诉B它应当如何完毕方式。也就是说三个聚合根即使在一块工作,可是它们的职分照旧是分离的,各自履行各自的允诺,只是在共同扶助完结任务而已。

  12)最后大家看看关于Permission的空定义:

  讲解完空对象的编码表达,相信大家应该有一个迷惑,那就是“大家究竟为什么要兑现空对象?”。固然眼前的课程里讲过空对象的盘算,不过现实它对我们编写程序有哪些本色上的扶持各位应该还不知情。

  第三个领域模型的代码讲解工作就到此甘休了。大家有没有对CA提供的开发形式很感兴趣呢?有没有想快捷采用CA开发项目标冲动?先别急,在下一章节里我们会详细讲述怎样接纳Permission对象落成应用命令调用、怎么着构建Permission的囤积以及怎么着布置CA的服务站点。学习完那一个,你就足以初叶尝试采取CA实践开发工作了。

  之所以在Permission里定义Declare马克(Mark)edCode是因为我们认为不是负有的需要都是平素利用权力来限制用户访问的,有时候仅看清用户是或不是属于某个角色即可验证访问安全。所以大家不必为各类Permission都填写标识码,只必要为站点里需求接纳的权力填写标识码。那也是为何马克edCode属性没有标记[NotEmpty()]的案由,它可以是空的。为了在天地对象Permission里优秀“标识码不是必须的”那一点特征,我们分外编写了Declare马克edCode属性,以便在急需的时候可以直接判断。事实上,在以后的小日子里,该属性大约从不被用到过,写那段代码是我们随手而为之,你可以认为那是一种过度设计,不过那无伤大雅,因为完毕Declare马克edCode属性的血本很低。当然,指出我们在推行项目时仅在有必不可少的时候才为世界对象编排额外的性质或格局,不要过分设计,这么些话题后文会有详述。

  PermissionSpecification继承了泛型基类ObjectValidator<Permission>,这是目的验证器的底子类,继承那一个目的可以省去大家处理任何细节的流年。泛型参数里记得填写聚合根的类型,也就是Permission。

6. 为世界模型Permission编码

  不会,性能问题是个综合性话题,并不是三遍性查的数据愈多属性就越高。事实上,数据库IO读取是以页为最小单位的,每个页8K(那里以SQL
Server
2005为例,其他数据库日照小异)。也就是说,只要您执行查询操作,尽管你询问的数额唯有1个字节,数据库仍旧会读取一个8K的数据页(数据库最小读取页为8K,实际工作时日常也以64K为单位查询),那么您考虑,借使大家读取的数量体积越小,大家得以加载的数目是还是不是愈多?那也是怎么数据库设计里有个重点的口径,设计字段类型的时候占用字节数越小越好。因为字段类型占用字节数越小,每行数据占的体积就越少,那么数据库IO四次每页可以包容的数据行数就越多:1行多少体积是1K,8K就足以加载8行数据,可是1行数据如果是500字节,那么8K就能够加载16行数据,所以数据类型占用的字节数小,大家四遍IO读取的一蹴而就数据就越来越多,那样就裁减了IO读取的次数,升高了系统性能。

  事实上你一点一滴可以这么做,那也是CA提供的正儿八经写法。只是考虑到程序员们在其余框架里习惯对性能直接打特性了,所以CA才提供了包容性的写法,即:直接在性能上标记特性以便更详实的叙述领域属性的定义。在少数意况下,你不得不将特色标记在天地属性定义上,比如在为对象静态增添属性时。因为我们先是个代码示例还未涉及到那方面的话题,所以大家的代码里是鲁人持竿程序员的习惯将特色写在圈子属性上,而非领域属性定义上。

  那么在CA里吗?在CA里有三种处理办法,大家脚下只谈谈最普遍的率先种:删除外部内聚根不会潜移默化内聚模型的聚合根极其成员!也就是说,一篇文章所属的分类目的被删除了,默认景况下该分类下的篇章是不会删除的,你照样可以在篇章列表里询问到已被删去分类的篇章新闻(因为大家查询著作列表并不必要inner
join小说分类,所以丝毫不影响查询结果)。那么,那时候Article对象的Category属性值是怎么啊?就是ArticleCategory.Empty啊,也就是空的稿子分类目标!前文说过,空的靶子也是有任务的,由此你使用代码:article.Category.Name的时候结果是空字符串,并不会报错,空的稿子分类的职责之一就是提供分类名称的性状,尽管是空字符串也是一种奇特的归类名称。那么在表现层,文章列表分类那一列里,没有分类的文章呈现的归类名称就是空的字符串。你也得以使用
article.Category.IsEmpty()
判断项目是或不是为空,以便输出“小说未分类”的字样提需要UI显示。在这种情势下,你甚至足以新建分类目的,然后将未分类的稿子再一次分配新分类。

  我们再来探究这项属性被标记的3个特征。请小心,那3个特点都是用来定义Name的,我们事先涉嫌过,大家使用DomainProperty类型来发布领域属性的概念,那么为何这边的3个性状被标记在Name上,而不是一向标记在NameProperty上呢?例如:

  只然则.NET提供了性能的写法,Declare马克(Mark)edCode不须要任何参数,内部贯彻也很不难,所以大家就将其定义成了性能的写法,那样调用起来相比较便宜、直观,例如:

    #region 空对象

    private class PermissionEmpty : Permission
    {
        public PermissionEmpty()
            : base(Guid.Empty)
        {
            this.OnConstructed();
        }

        public override bool IsEmpty()
        {
            return true;
        }
    }

    public static readonly Permission Empty = new PermissionEmpty();

    #endregion

  public static readonly Permission Empty = new
PermissionEmpty();
请注意访问修饰符public代表外界得以接纳Permisson.Empty属性,此外,请注意Empty成员的品种为Permission而实例为PermissionEmpty,那样对于外界代码而言既隐藏了PermissionEmpty的定义又揭橥出了Permission的Empty成员。

  与划分子系统的思路一致,大家以最简单易行、最独立的事物作为突破口。简单是指事物在一定领域里的特色相比较少,没有那么复杂。很显眼,权限是最简单易行、最独立的,它不借助于于账号、角色而独自存在,而且从当前采集到的必要来看,权限的特征只须要知名称即可。所以大家品尝以权力(Permission)为聚合根成立首个内聚模型。请各位注意,我在此处用“尝试”一词表明要做的做事,因为大家并不可以担保当前做的仲裁100%是对的,可是勇敢的去尝尝总比心神不定、不敢迈出第三个步履、始终原地踏步要好的多。所以各位在实践的时候,即使有了灵感、有了大致的思路,即便思路还不够完善、不够显然,你也得以大胆的去品尝,CA可以保险尽管设计有误也能登时校订。使用CA开发品种的历程就是不断的在条分缕析、设计、实践、改良中一再迭代的进程,最终你会提炼出与事物本质特征相符的领域模型。

  select * from article inner join
articleCategory on article.categoryId = articleCategory.Id;

   NameProperty
= DomainProperty.Register<string,
Permission>(“Name”); Register是DomainProperty提供的静态泛型方法,该办法的再次来到值是DomainProperty的实例。第四个泛型参数string,表示属性值的花色为string,第一个泛型参数Permission表示该领域属性属于类型为Permission的领域对象。参数“Name”表示属性的名目为Name。

   除了马克edCode的概念外,代码段里还编写了Declare马克(Mark)edCode属性,严酷的讲,Declare马克(Mark)edCode应该是一个天地点法,应该是这般的格式:

   2)namespace AccountSubsystem
代表Permission对象处于账户子系统内。请注意子系统的命名约定:在子系统的实在名称上追加Subsystem后缀组成。例如:UserSubsystem(用户子系统)、CarSubsystem(车辆子系统)。

  首先,请留心访问修饰符internal,那象征该领域属性仅程序集内部可知,你也得以根据须求设置成为public、private,大家提议你在不精晓哪些挑选的时候就填写private,确保世界属性的定义仅对象内部可知。那里补充一个目的考虑的小技巧:不论是艺术或者属性使用个人定义意味着该目的不对外作出任何承诺,仅内部使用。对外承诺的越来越多(public修饰符)对象急需履行的职分就越来越多,就越复杂,复杂就不难失误。因而尽可能的利用private,只在要求的时候利用public是一个脍炙人口的编程习惯。大家为Permission设计的领域属性Name是internal而不是private是因为PermissionSpecification那么些规则要求选用它(详见往日的代码贴图),所以将原先私有的拜会修饰变为了程序集内部可知的。

  private class PermissionEmpty :
Permission
 请留心访问修饰符是私有的private,表示大家对外不直接当面PermissionEmpty,要使用空对象必须以Permission.Empty的语法。那可以有限支撑空对象是全局唯一的,不会有多个实例,升高系统性能。由于空的权限对象依旧权力对象,所以PermissionEmpty继承自Permission。其余,空对象类型的命名大家约定为世界对象名称追加Empty后缀。

  protected override void
Validate(Permission obj, ValidationResult result)
是派生类必须重写的章程,你要在这中间编写验证逻辑。 ValidationResult表示的是评释结果的对象,你可以拔取那些目的追加错误消息。在本示例里只是简单的将该参数传递给了Validator使用。

   9)再来看看关于马克edCode的代码段:

  由于我们平日会遇到某个属性不可以再一次出现的急需(比如用户名无法重新等),由此CA提供的Validator工具对象里定义了CheckPropertyRepeated方法,用于检查属性的值是不是再一次。Validator.CheckPropertyRepeated(obj,
Permission.NameProperty,
result);就是检核查象obj的Name属性的值是或不是早已在其他对象里出现了,假如出现了,result参数里就会增加一条错误,该错误最终会由CA框架处理,抛出荒谬格外。用该方式判断属性重复规则很便宜,请小心Validator的概念在CodeArt.DomainDriven.DataAccess程序集中,也就是说,这一个工具类是由基础设备层提供的,不是世界模型层的情节,因为判定属性值是还是不是再度必要由仓储的达成襄助,技术上的缘由导致该工具类只可以出现在基础设备层。其余,大家可以定义尤其错综复杂的稳定规则,比如一个班的学习者人数不可能当先50人等。在此起彼伏的示范里大家再演示尤其错综复杂的情况。

SQL Server 1 

  那么,以上说的这整个和空对象有哪些关联吧?大家试想一下,假使大家删除了稿子分类,该如何处理分类下的篇章?传统格局下,那种情状须求级联删除,顶多在剔除操作从前UI会给个温馨的提醒“删除该分类会去除分类下的篇章,您确定删除吗?”,因为尽管你不删除分类下的文章,那么当您inner
join
分类表的时候,由于尚未分类数据的存在,小说查询不到了,小说成了系统里不可知的“脏”数据(是的,你可以用left
join来拍卖那种情况,然则left join又会带来查询结果中分类音信为
null的劳动,你又得处理那种状态,其它,很多动静下不是你想用left
join就可以用的,涉及到的问题多多,那也是为什么基于数据表处理事务很简单混乱的缘由之一)。

    if(permission.DeclareMarkedCode)
    {
        //该权限定义了标识码
    }
    else
    {
        //该权限没有定义标识码
    }

  与Name属性类似,同样的语法定义了马克(Mark)edCode(标记码)属性。在率先次编码Permission对象的时候并从未这么些特性,随着项目标有助于大家发现有必不可少为Permisson对象追加标记码机制。

  7)上边来看代码段:

  大家从UI和数据库的角度解析了空对象起到的意义。现在大家透过现象看本质,从规划思想上对空对象的市值举办计算:在领域模型层,大家通过内聚模型分离关心点,每个关心点的内聚性极强,每个内聚模型只用关爱内部的拍卖细节,不必关怀外部模型是怎么兑现的。不过一个关注点的其中频仍又会拔取此外一个关切点帮衬自己做到某项职责,那也就是为什么内聚模型内部有可能会引用外部聚合根的原故。上述示范里,
作品的聚合根(Article)就使用了小说分类的聚合根(ArticleCategory)来协助文章处理有关“小说所属分类”的关怀点,那令小说模型不必处理分类事物的落到实处细节,将分类那么些关心点全部信托给小说分类处理,那也使得日后当小说分类需求追加新的风味或者修复BUG或者变化要求都不会影响到小说的落到实处,程序员不必因为修改某个内聚模型而想不开牵扯到此外的内聚模型,极大增强了系统的油滑和稳健性。当一个篇章对象的实例引用的篇章分类被去除了,该小说对象仍然得以健康干活,造成这种美好现象的根本原因是小说使用了某个分类实例来满意它对分类的内需,该分类实例遵循了一项约定,那项约定的内容就是ArticleCategory类型定义显示出来的,例如分类名称Name就是预定之一,表示分类可以提供名称那项特色。那么当该分类实例被去除了,文章实例引用的归类就会被活动切换来空分类那些实例上,该分类实例同样遵守了ArticleCategory类型约定,因为空的作品分类也是持续于ArticleCategory类型的,所以空的篇章分类替代了已被删去的稿子分类,继续实施分类的职分,这令系统越发健康,不会因为贫乏了哪个人而夭折!那跟使用接口的原理是同一的,大家可以每天为某个内聚模型里引用的表面聚合根切换实例,那就相当于为接口切换了贯彻均等,只要坚守了接口约定,系统一样可以稳定的运作!

  CA规定每一个天地对象都应当有一个空对象定义,并且以名称为Empty的静态成员发布出来。也就是说,我们可以利用领域对象.Empty的款式利用这些小圈子对象的空对象,Permission.Empty就表示Permission的空对象。上述代码是编写空对象的永恒方式,大家请依照该形式编写空对象,表达如下:

  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:

  判断Permission是还是不是为实体对象的按照之一就是外部东西是不是须求一向找到它。那里的表面东西是指”应用层”和”领域模型层里除Permission以外的天地对象”。首先,要咬定角色是还是不是拥有某项权限,大家自然必要树立角色和权力的引用关系,由此可以算计出,权限应该是索要被外表对象角色所从来引用的(注意,由于角色这一东西还未曾起来筹划,所以那边我们只是做的比方,帮衬大家看清Permission的安插)。其它,权限的称号、描述等音讯须求由系统的使用者去平素填写或变更,所以我们得以想象得到,应用层须求根据标识符获取Permission对象,将其读取后表现相关信息给系统使用者查看(注意,我们那边是借助UI操作的形式来扶持我们看清Permission是不是为实体对象,再度宣称,领域模型的创立不仅是为着满意UI操作,可是正确的圈子模型一定可以完全满意UI操作,由此,借助它来协理大家分析世界对象怎么着筹划是足以的,只是小心要适中,不要局限于某一种UI操作来统筹目标。)。所以大家看清Permission是实体对象,它拥有成为聚合根的主导规则。

  大家从工作的角度解析对象,摸索出事物的本质,然后为其制定一定规则。这和表设计是二种不一致思考问题的不二法门。尽管有可能三种实施办法下偶尔得到了一如既往的结果(那里指的结果只是是储存结果,因为世界对象最终也是要被投入到仓储的,用数据库做仓储的兑现依旧急需为该领域对象设计表,那么咱们设计好的目标在映射表的时候,有可能对象表的安顿性会与传统开发设计出来的表相同),但那不代表对象设计和表设计是一模一样一件事,事实上大部分处境下相互设计的结果是一点一滴分歧的。所以,领域对象的安顿性和数据库表的布置不可能歪曲,用数据库持久化领域对象真正需要统筹表,不过表的字段、约束等规则都是看重于世界对象的宏图,先有天地对象才会有数据库里的表,即便你不要数据库存储领域对象,领域对象仍然存在,它依然得以处理事情!

     [SafeAccess]由命名空间CodeArt.Concurrent提供并雄居CodeArt程序集内。该特性提醒对象是出新访问安全的,也就是多线程访问安全的。任何项目只要标记那么些特点,当CA内部在布局该品种的实例时,就会缓存实例。当须要下次创建时直接回到该实例,因为对象是出新访问安全的,只须要一个实例即可。因而,当您设计的品种是出新访问安全的同时你也冀望它以单例的样式出现,那么就足以为品种标记该特性。那里的PermissionSpecification对象没有其余性质成员,内部的法门已毕也与气象无关,因而可以看作单例的款式出现,所以标记了该特性。该特性可以增进应用程序性能,重复使用同一个对象。

  1)using CodeArt.DomainDriven;
表示引入CodeArt.DomainDriven命名空间,该命名空间提供了世界规划的技术支持。要拔取该命名空间你要求在账户子系统中引用CodeArt.DomainDriven的次序集:

public class Article:AggreateRoot<Article,int>
{
      其他成员的定义.....

      Public ArticleCategory Category{ get ; set;}

      其他成员的定义......     
} 

public class  ArticleCategory:AggreateRoot<ArticleCategory,int>
{
      会有多个成员的定义.....
} 

  在CA里是用一个一体化政策来幸免那类问题,空对象是这么些策略的一个环节。首先,咱们获得对象只可以通过仓储查询聚合根。在储存里面举办查询的时候,聚合根的成员也会被随即加载(除非你设置了推迟加载,这些话题持续章节里有详实表达),也就是说,当我们加载聚合根的时候,仓储会以聚合根对应的数据表为from表,inner
join 内聚成员表,
然后依照查询的数额结果构造聚合根对象和内聚成员对象,并且填充它们的属性值。(那是一种简化的表明,实际上CA提供的ORM的中间机制相比复杂,加载对象的时候会进展缓存、并发控制、对象继承和扩展的分辨等工作)。由于聚合根的分子类型数量会很少,那里大家从未用“一般很少”来形容,因为随便需求多么复杂,大家一味能够保障聚合根的积极分子类型数量差不离不当先3个。也就是说,在储存之中inner
join 的表不会超过3个,大部分气象下仅0-2个。

  在考虑将Permission作为聚合根后,大家依然要对这一裁决提议质疑,要反问自己Permission是值对象仍旧实体对象。假设Permission是值对象,那么它就不可以同日而语聚合根了,因为聚合根必须是实体对象。使用CA做开发,大家要善用运用这种思维技巧:先按照脑海的“嗅觉”做出规划上的判断,再反问自己各样问题以便验证或推翻那项判断。那种先做决策再试图推翻的构思格局会带给您意外的大悲大喜,如若你推翻不了它,讲明所做的决定就是对的,反之就须求改进那项决定,然后再去想方法推翻新的裁定,一贯到您找到不可能推翻的核定为止。

  其中Article有项世界属性叫Category(小说所属的分类),类型为ArticleCategory。也就是说以Article为聚合根的内聚模型有个称呼为Category的分子类型为其它一个内聚模型的聚合根ArticleCategory。要是ArticleCategory模型里也有和好的成员,比如自定义文章模板(也就是说,发表在这么些分类下的稿子必须信守的始末模板)等。那么一旦大家加载聚合根Article,当仓储查询数据的时候是还是不是要询问成员Category,由此要inner
jion 文章分类的表,由于小说分类的也有成员,那么还要inner join
文章分类的分子对应的表,导致最终以文章为聚合根的询问 inner join
的表数据也会众多,超越10个左右吗?

/// <summary>
/// 权限名称
/// </summary>
[PropertyRepository()]
[NotEmpty()]
[StringLength(2, 25)]
internal static readonly DomainProperty NameProperty = DomainProperty.Register<string, Permission>("Name");

SQL Server 2

  表明了CA里怎么验证目的固定规则的代码后,大家回过头来谈谈那上头的商量问题。我们可能觉得大家那边的小圈子属性以及性能验证和观念支付格局里的表设计是四次事,比如在表permission里有个名称为name的字段,这些字段要有唯一性约束,并且长度是50以内。不可不可以认,从这些角度来看,领域对象和数码库表设计真正有点相似之处,然则彼此完全不是同一个定义。

  8)以上介绍了世界属性的连锁话题,现在我们回头看看以前涉嫌的对象条件的代码落成:

    public bool DeclareMarkedCode()
    {
        return !string.IsNullOrEmpty(this.MarkedCode);
    }

  执行该sql就足以一遍性查获小说和小说所属分类的新闻,而在刚刚的例子里,如若大家先加载了Article对象,然后代码执行在某个地点时接纳了Category属性,导致再一次加载分类新闻,举办了二次询问,那样性能会不会比一直实施sql差呢?

    [ConstructorRepository()]
    public Permission(Guid id)
        : base(id)
    {
        this.OnConstructed();
    }

  那么我们在ValidatePermission方法里传递权限的号码吧?要精晓数码是不会被改动的。不过利用编号有多个问题,1.编号是GUID(你也足以设置为整型),那种数字化的值不够直观:ValidatePermission(125),你能收看该办法是讲明什么权限吗?2.一旦我们把查看员工音讯的权位误删除了,然后大家又再一次创立该权限,那么“查看员工音讯”的权杖编号就变了,我们照样要在页面里改验证代码。

 

  要回应这么些问题,首先请回想一下,在观念支付里我们常常会蒙受删除某条数据要级联删除相关的数量,而除去相关的数目又要级联删除相关数据的连带数据。读取数据也一致,大家平日inner
join多个表,有的种类中一行sql代码甚至会连续10个以上的数据表。连接这么多的表就标明着表与表之间有耦合关系,一旦中间一个表暴发变化,必要吝惜的地点就会众多,这往往令程序员焦头烂额。但是,那个与空对象有怎么样关联吧?

  10)Description属性表示权限的描述,系统管理员在设置权限的时候可以填充简而言之述以便查看使用,该领域属性至极不难不做过多的表明。

  static readonly
是必不可少的修饰符,表示领域属性是静态且不得更改的。定义它为静态的是因为世界属性是对事物某项特征的讲述,学生的年华就是属于学生那几个事物“按年统计存在的岁月”的特性,是有所的学习者实例都会有些特征,而不是某个学生独有的。由此年龄的小圈子属性为静态的。

  [ConstructorRepository()]特点由CodeArt.DomainDriven.DataAccess提供,属于CA框架里ORM的概念。该特性表示领域对象被贮存创造时调用的构造函数是Permission(Guid
id)的本子。由于Permission相比较不难,所以它的构造函数只有一个。有时候我们会为世界对象编排七个构造函数,这时候标示出仓储使用哪个构造函数就很紧要了。同样的,即使你不行使CA提供的ORM那么可以不标记该特性。

  引起该问题的真面目原因是什么样吗?是因为在站点里有权力上的必要,那是站点在支付时期一定的硬性要求,是硬编码完成的,比如: ValidatePermission(“查看员工信息”)就是硬编码完毕验证登录人是或不是有“查看员工音讯”的权力。而我辈规划的权柄机制是一个通用型的,是为着在三个站点里都足以引用权限验证的机制。由此,大家提供门户服务的后台入口,由系统管理员可以安装每个站点自己的权杖信息,并交由给门户服务保存,比如:A站点是一个OA系统,所以大家为A站点成立了“查看员工音信”和“成立员工音信”那三个权力。请牢记,门户服务本身是个单身站点,你在A站点提交权限音信给门户服务,门户服务就为A站点保存了那两项权限的新闻。权限的多寡是身处门户服务的蕴藏里,不是在A站点里。其它,B站点是一个情报项目,所以大家又在门户服务里为B站点保存了“小说管理”的权限。那么,你可以在A站点和B站点里,调用门户服务提供的证实权限的API,来判断当前登录人是还是不是有指定的权柄。那样我们七个类型都得以共用门户服务,大家不必在新品类里为权力机制再度付出劳引力。

  不会。因为Category是作品内聚模型以外的外表聚合根,那种表面聚合根默认情状下是不须求加载的,唯有当你须要的时候才会重复加载。也就是说,当你首先次加载Article对象的时候,Category属性根本就平昔不被读取而是当您选用类似的语法article.Category访问分类属性的时候,CA才会调用仓储加载聚合根ArticleCategory对象。也许你会问那样会不会促成性能问题?在传统支付里我们得以采用一句sql加载出文章和小说分类的音讯:

  11)再来看看Permission的构造函数的代码:

  账号、角色、权限是账户子系统里已知的3个东西,而一个子连串内部可以有三个内聚模型,所以我们先是要商量的题材是:以哪个人为聚合根创造第四个内聚模型?

  但是有一种意况相比较独特,那就是有可能内聚模型a内部引用了内聚模型b里的聚合根B。比如说:作品(Article)对象是一个聚合根,作品分类(ArticleCategory)也是一个聚合根,大家不要纠结于为何如此设计,小说系统的陈设前面会有案例分析,近年来我们就认为已经筹划成那规范了。示意代码如下:

/// <summary>
/// 权限名称
/// </summary>
[PropertyRepository()]
[NotEmpty()]
[StringLength(2, 25)]
public string Name
{
    get
    {
        return GetValue<string>(NameProperty);
    }
    set
    {
        SetValue(NameProperty, value);
    }
}

  最终重复提示各位,即便在解释空对象的效应时我们将世界对象和数据表的完毕作了大气的自查自纠,然而世界对象和数据表仍旧不是同一个概念,他们有本质上的分歧。领域有目的是兼具职责的。由于Permission对象很简短,所以很便利大家作为第三个代码示例做阐明,不过Permission并无法浮现出职务的更加多特点,那令它看起来有些类似“表类”那样的贫血模型,可是在前边的学科里大家会创建越多的天地对象并演示出天地对象时期如何协同工作的,那样大家的认识会更加深切。

  通俗的讲,大家运用 internal static
readonly DomainProperty NameProperty =
DomainProperty.Register<string, Permission>(“Name”);
这句代码为世界对象Permission注册了一个Name属性的概念,那些概念里证实了世界属性的名号为Name,领域属性的值类型为字符串,领域属性属于世界对象Permission。关于那地点愈来愈多细节的研商请继续看后文。

  [StringLength(2,
25)]那一个特点我们一定都能明了,表示字符串的微乎其微长度和最大尺寸。

  IsEmpty方法是DomainObject的提供的基类方法。所有的天地空对象定义里都要重写IsEmpty方法,并且重返true作为结果。

  构造函数代码不必多说,我们即使注意一点,空对象也是小圈子对象,由此也要依照约定调用
OnConstructed 方法以便提醒框架对象已结构完结。

  4)紧接着我们为Permission类又标记了[ObjectValidator(typeof(PermissionSpecification))]。正如其名,ObjectValidator表示对象验证器,还记得大家在前文里说过“每个领域对象都具备注解固定规则的力量”这一个圈子规则吧?ObjectValidator就是用于对象验证的,为对象标记这一个特点并且传入参数PermissionSpecification,就意味着Permission对象急需满意项目名称为PermissionSpecification的标准。在PermissionSpecification的代码里,大家会编码定义规则的底细。CA强调每个对象都应有满足1个或者多少个要求满意的规则,所以您可以流传八个原则体系给ObjectValidator特性。当对象被交给给仓储的时候,那么些原则会被机关验证。PermissionSpecification的代码如下: 

SQL Server 3

   仅描述事物的特性但不去行使它是尚未意义的。上述代码就将世界属性定义NameProperty应用到了Permission实例上。令Permission类型一旦实例化了,就拥有提供本人名称的能力。Name就是Permission的圈子属性。请留意Name属性的Get和Set方法,由于大家运用了世界属性的概念,所以当您为世界对象编排领域属性代码的时候,请直接利用语法GetValue<T>(DomainProperty)
和 SetValue(DomainProperty, value)
来完毕世界属性的Get和Set方法,不要在那三个方法里编写其余的代码,那也是选拔CA的规则之一。GetValue的泛型参数T表示需求取得属性的值的类型,DomainProperty参数表示领域属性定义(在那边是事先编写的NameProperty)。SetValue这么些主意的调用比较简单,在此可是多的辨证。

  [NotEmpty()]该特性提醒属性的值是无法为空的,请留意,之前大家谈论过Not
Null的话题,在CA的天地世界里,所有领域对象以及世界属性值都不可能有null值,所以固然你不写NotEmpty也意味Name属性无法有null值,然而NotEmpty表示的意思是差异意为空值,对于字符串来说””或者string.Empty表示的就是空值。因而那里的情趣是字符串属性Name不容许是空的,必须有起码1个或1个以上的字符。

  6)internal static readonly
DomainProperty NameProperty = DomainProperty.Register<string,
Permission>(“Name”);那句代码很主要,那是CA里登记领域属性定义的主意,从概念上讲世界属性是指可以显示事物在某一天地本质特征的属性。从代码实现上来说,与常见属性相比较,领域对象要对天地属性有更强的控制性,那反映在性能哪一天被转移、属性是还是不是为脏的(与数据存储里的多寡不雷同就是脏的)、可以以不损坏原有对象的代码景况下增添一个世界属性、重写或增添属性的GET或SET方法等。这个CA都曾经做了丰富的支撑,你只需求遵守语法编写定义领域属性的代码即可。

  [PropertyRepository()]特点,与事先涉嫌的ObjectRepository类似,该特性由CodeArt.DomainDriven.DataAccess提供,表示该属性是足以被积存的。也就是说你为属性打上这么些特点,CA的ORM模块在储存对象的时候就会考虑将该属性的值存入到仓储里。该特性不是世界模型必备特性,假诺您要自己完毕目标的持久化操作可以不必标记该特性,但那往往没有须求。

  在认证该代码往日,我们先搞精晓“领域属性定义”和“领域属性”的不相同,定义是对世界属性的特征描述,比如世界属性的名号为Name,这就是概念的一局地。大家那边的代码是认证什么定义领域属性的。至于领域属性的应用在前面的代码段中会有证实。

SQL Server,  关于空对象最终一个注意事项是“请将空对象的概念放在所有的圈子属性的概念之后仍然直接放在领域对象代码的底层”,有那项约定不是因为安顿上的题材而是在代码完成上,如若不将空对象放在领域属性的定义之后,有可能引起一个技术问题,由于那个技能问题隐藏得相比较深,所以在此间可是多表达,当我们完全了解了CA的工作格局后,我再来剖析框架的兑现细节,那时候再解答引起问题的来头。在此地各位只用记住需求遵从这么些约定就能够了。 

相关文章