第二部分:领域对象的生命周期几个关键元素聚合、工厂(Factory)、资源库(Repository)使用时机和应用场景

目录

问题说明  

1.聚合(Aggregate)

2.工厂(Factory)

3.Repository(资源库)

总结


问题说明  

  领域对象的生命周期的挑战
   1.在整个生命周期中维护完整性;
   2.防止模型陷入管理生命周期复杂性造成的困境中;

  有三种方式可以解决以上问题

     使用工厂(Factory)来创建和重建复杂对象和聚合,从而封装它们的内部结构。在生命周期的中间和末尾使用资源库(Repository)来提供查找和检索持久化对象

接下来详细介绍

1.聚合(Aggregate)

    它通过定义清晰的所属关系边界,并避免混淆、错综复杂的对象关系网来实现模型内聚。聚合模式对于维护生命周期各个阶段的完整性具有至关重要的作用。

   聚合就是一组相关的对象的集合我们把它作为修改数据的单元。每个聚合都有一个跟(Root)和一个边界(Boundary)。边界定义了聚合的内部都有什么根则是聚合所包含的一个特定实体对聚合而言,外部对象只能引用根(通过唯一标识),而边界的内部对象之间则可以相互应用(通过引用),聚合保障了领域固定规则的一致性和完整性。

   什么是固定规则?

 
    固定规则(variant)是指在数据变化时必须保持的一致性规则,其涉及聚合成员之间的内部关系。而任何夸越聚合的规则将不要求每时每刻都保持最新状态。通过事件处理、批处理或其他更新机制,这些依赖会在一定的时间内得以解决。但在每个事务完成时,聚合内部所应用的固定规则必须得到满足。


    聚合的一些使用规则


    1.根实体具有全局标识,它最终负责检查固定规则。
    2.根实体具有全局标识,边界内的实体具有本地标识,这些标识只在聚合内部才是唯一。
    3.聚合外部的对象不能引用除根实体之外的任何内部对象。根实体可以把对内部实体的引用传递给他们。但这些对象只能临时使用这些引用,而不能保持引用。根可以把一个值对象的副本传递给另一个对象。而不必关心它发生了什么变化,因为它只是一个值,不在与聚合有任何联系。
    4.作为上一条规则的推论,只有聚合的根才能直接通过数据库查询获得。所有其他对象必须通过遍历关联来发现。
    5.聚合内部对象可以保持对其他聚合根的引用。
    6.删除操作必须一次删除聚合边界之内的所有对象。(由于除根以外的其他对象没有外部引用,因此删除根以后,其他对象均会被回收)
    7.当提交对聚合边界内部的任何对象的修改时,整个聚合的所有固定规则都必须被满足。

   总结来说:我们应该将实体和值对象分门别类的聚集到聚合中,并定义每个聚合的边界。在每个聚合中,选择一个实体作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保聚合中的对象满足所有固定规则,也可以确保在任何状态变化时聚合作为一个整体满足固定规则。

2.工厂(Factory)

    当创建一个对象或者创建整个聚合时,如果创建工作很复杂,或者暴露了过多的的内部结构,则可以使用Factory进行封装,它的作用就是隐藏创建对象的细节。
   

     对象的功能主要体现在其复杂的内部配置以及关联方面。我们应该一直对对像进行提炼,直到所有与其意义或者在交互中的角色无关的内容被完全踢出为止。一个对象在它的生命周期中要承担大量的职责。如果再让复杂的对象负责自身的创建,那么职责过载将会导致问题。装配复杂的复合对象的工作最好与对象要执行的工作分开。

    不好的设计


      1.对象的创建在客户端(应用层)
        让客户端直接负责创建对象会使客户的设计陷入混乱,并且破坏被装配对象或者聚合的封装,而且导致客户端与被创建对象的实现之间产生过于紧密的耦合。


   好的Factory需要满足两个基本需求


      1.每个创建方法都是原子的,而且要保证被创建的对象或者聚合的所有固定规则。
        Factory生成的对象要处于一致的状态。在生成实体时,这意味着创建满足所有的固定规则的整个聚合,但在创建完成后可以向聚合中添加可选元素。在创建不变的值对象时,这意味着所有属性必须被初始化为正确的最终状态。如果Factory通过其接口收到一个创建对象请求,而它无法正确创建这个对象,那么他应该抛出一个异常,或者采用其他机制,以确保不会返回错误的值。


      2.Factory 应该被抽象为所需的类型,而不是所要创建的具体类。

  

   如何选择Factory及其应用位置


      1.如果我们要向已经存在的聚合添加元数,可以在聚合的根上创建一个Factory Method。这样就可以把聚合内部的实现细节隐藏起来,使任何外部客户看不到这些细节,同时使根负责确保聚合在添加元素时的完整性。


      2.在一个对象上使用Factory Method,这个对象与生成另一个对象密切相关,但它并不拥有所生成的对象。当一个对象的创建主要使用另一个对象的数据(或许还有规则)时,则可以在后者的对象上创建一个Factory Method。这样就不必将后者的信息提取到其他地方来创建前者,这样还有利于表达前后者的之前关系。


      3.Factory与被构建的对象之前是紧密耦合的,因此Factory应该只被关联到与被构建对象有着密切联系的对象上。当有些细节需要隐藏(无论是隐藏具体实现还是构造的复杂性)而有找不到合适的地方来隐藏他们时,必须创建一个专用的Factory对象或Service。整个聚合通常由一个独立的Factory创建,Factory 负责把对根的的引用传递出去,并确保创建出来的聚合满足固定规则。如果聚合内部的某个对象需要一个Factory,而这个Factory又不适合在聚合根上创建。那么应该构建一个独立的Factory。但仍应该遵守规则--把访问限制在聚合内部。并确保从聚合外部只能对创建的对象进行临时引用。

  什么时候只需要使用构造函数?


     如果任何时候都使用Factory实际上会使那些不具有多态性的简单对象复杂化。
     以下情况最好使用简单的、公共的构造函数
     1.类是一种类型。它不是任何相关层次结构的一部分,并且也没有通过接口实现多态性。
     2.客户关心的是实现,可能是将其作为选择Strategy的一种方式
     3.客户可以访问对象的所有属性,因此向客户公开的构造函数中没有嵌套的对象创建。
     4.构造并不复杂;
     5.公共构造函数必须准守与Factory相同的规则,它必须是原子操作,而起要满足被创建对象的所有固定规则。

    不要在构造函数中调用其他类的构造函数,构造函数应该保持绝对的简单。复杂的装配,特别是聚合,需要使用Factory.

  注意事项


      在设计Factory的方法签名时,无论是独立的Factory还是 Factory Method,都要记住一下两点
      1.每个操作都必须是原子的
        我们必须在于Factory一次交互中把创建对象所需的所有信息传递给Factory,同时必须确定当前创建失败时候将执行什么操作,比如某些固定规则没有被满足时,可以抛出一个异常或者仅仅返回一个null。


      2.Factory将于其参数发生耦合
        耦合程度取决于对参数的处理。如果只是简单地将参数插入到构建对象中,则依赖度是适中的。如果从参数中选出一部分在构造对象时使用,耦合更紧密。
      使用抽象类型的参数,而不是他们具体类。Factory与被构建对象的具体类发生耦合(方法内部),而无需与具体的参数发生耦合。


   固定规则的相关逻辑应放到哪里


     Factory负责确保它所创建的对象或者聚合满足所有固定规则。然而把应用于一个对象的规则迁移到该对象外部需要三思。Factory可以将固定规则的检查工作委派给被创建对象,而这通常是最佳的选择。但是有时也需要有打破这种的情况,把固定规则的相关逻辑放到Factory。

    比如1.为了让被创建对象的职责更加清楚。对于聚合的规则来说尤其如此(这些规则会约束很多对象),固定规则的相关逻辑却特别不适合放到那些与其他领域对象关联的Factory Menthod。再则,2.如果逻辑在对象的有效生命周期内永远也不被用到,那么对象就没有必要携带这个逻辑,这种情况,放在Factory里是最合适的。比如可能实体的标识属性的赋值需要满足一个固定规则。

   实体Factory和值对象Factory
   

    实体Factory和值对象Factory有两个方面的不同,由于值对象是不可变的,因此,Factory所生成的对象就是最终形式。所以创建对象时需要被创建对象的完整描述。而实体Factory则只需要具有构造有效的聚合所需的那些属性。对于固定规则不关心的细节,可以之后再添加。


    到目前为止,Factory只是发挥了他在对象生命周期开始的的作用。用于重建的Factory与用于创建对象的Factory很相识,主要有以下两点不同


      1.用于重建对象的实体Factory不分配新的跟踪ID;


      2.当固定规则未被满足时,重建对象的Factory采用不同的方式进行处理,创建新对象时,如果未满足固定规则,将拒绝创建对象。

3.Repository(资源库)


     我们可以通过对象之间的关联找到对象,但当他处于生命周期中间时,必须要有一个起点,以遍从这个起点遍历到一个实体或值对象。

    无论要用对象执行什么操作,都需要保持对它的引用,那如何获得它的引用呢?

    方法有三
    1.创建对象:创建对象都会返回一个新的对象引用(Factory);


    2.遍历关联:以一个已知的对象作为起点,并向他请求一个关联的对象;(聚合根)尽量使用这种;
    3.搜索 (Repository)。

    要准守一个准则,除了通过根来遍历查找对象这种方法以外,禁止用其他方法对聚合内部的任何对象进行访问

    在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问,当很难通过遍历方式来访问某些聚合根的时候,就需要使用这种方式(搜索),他们通常是实体,有时是具有复杂结构的值对象,还有可能是枚举值。而其他对象则不宜使用这种访问方式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和聚合。技术基础设施和数据库访问机制的暴露会增加客户的复杂度吗,并妨碍模型驱动的设计。

    为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法(聚合或实体的保存和删除就是调这里的方法),用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方法,并返回属性值满足查询条件的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来,只为那些确实需要直接访问的聚合根提供Repository。让客户(应用层)始终聚焦于模型,而将所有对象的存储和访问操作交给Repository来完成。


     有此可见Repository 1.可以直接在应用层被引用和调用;2.方法的返回值是聚合根或者聚合根列表(领域对象),入参是领域对象或者对象的属性

     Repository的优点,包括


     1.它们为客户提供了一个简单的模型,可用来获取持久化对象并管理它的生命周期;


     2.它们使应用程序领域设计持久化技术(多种数据库策略甚至是多个数据源)解耦;


     3.它们体现了有关对象的访问的设计决策;


     4.可以很容易将它们替换为“哑实现”,以便在测试中使用(通常使用内存中的集合)


    所有的Repository都为客户提供了根据某种条件查询对象的方法,例如,通过标识来检索实体(几乎所有的Repository都提供了这种查询),通过某个特定属性值或复杂的参数组合来请求一个对象集合、根据值域(如时间范围)来选择对象,甚至可以执行某些属于Repository一般执职责范围内的计算。尽管大多数查询都是返回一个对象或对象集,但返回某些类型的汇总计算也符合Repository的概念,如对象条目,或模型需要对某个数值属性进行求和和统计

   

   使用Repository的一些注意事项


     1.对类型进行抽象,Repository“含有”特定类型的所有实例,但这并不意味着每个类型都需要一个Repository,类型可以是一个层次结构中的抽象(例如,动物可以是小狗或小猫的抽象)。类型可以是一个接口-----接口的实现者并没有层次结构上的关联,也可以是一个具体的类。


     2.充分利用与客户解耦的有点。我们可以很容易更改Repository的实现


     3.将事务的控制权留给客户(应用层)。尽管repository会执行数据库的插入和删除操作,但它通常不会提交事务。交给客户管理更合适。

     在很多时候我们要想办法在大方向上保持领域驱动设计的基本原理,而一些不符合的细节则不必过分苛刻。。。取舍问题。

     Repository和Factory的区别


      Factory负责处理对象生命周期的开始,而repository帮助管理生命周期的中间和结束。
      Factory负责对复杂对象的创建和重建,Respository负责查找已有的对象或对象集合,以及对象的存储和删除。
      Factory使用数据来实例化一个可能复杂的对象。如果创建的是一个新对象,那么客户知道在创建完成之后应该把它添加到Repository中,由Repository来封装对象在数据库中的存储。
·
      使用关系可以有如下几类


      创建新对象


       1. 应用层---发起创建领域对象调用-->调用Factory创建-->返回一个领域对象--->调用领域对象sava方法-->sava方法里调Repository存储方法-->Reposiory方法内调用相关转换进行类型转换(Model->DO)->Repository再调相关存储服务操作类(比如Mapper)


       2. 应用层---发起创建领域对象调用-->调用Factory创建-->返回一个领域对象--->调Repository存储方法-->reposiory方法内调用相关转换进行类型转换(Model->DO)->Repository 调相关存储服务操作类(比如mapper)。


      查询对象
       1.应用层--发起查询领域对象(参数通过标识符、属性值等)调用-》调用Repository查询方法--》方法内通过重建Factory进行聚合重建(比较复杂的对象,简单的直接通过转换类代替也可以)


       2.应用层--发起查询领域对象(参数通过标识符、属性值等)调用-》调用Repository查询方法--》方法内通过转换类进行转换成领域对象(DO->Model)

       还有一些转换:应用层到Api层有Model到Dto的转换,Repository里有 Model到DO的转换(保存)也有DO到Model的转换(查询)

总结

    使用工厂(Factory)来创建和重建复杂对象和聚合,从而封装它们的内部结构。在生命周期的中间和末尾使用资源库(Repository)来提供查找和检索持久化对象。更多相关内容看这里

你可能感兴趣的:(DDD,领域驱动设计,数据库,java,服务器,设计语言,设计模式,设计规范)