领域驱动设计(2)--Factory

时间:2022-10-30 15:55:11

注:基于聚合的思路,将一个业务相关的对象组成Aggregate,基于Aggregate来进行操作,就是Factory和Repository.

一、主题

领域模型有自己的生命周期,将特定生命周期转换的复杂性封装起来,是很重要的

Factory是封装生命周期的创建阶段;

Repository是封装生命周期的查询和重建,结束。

二、分论

2.1 Factory

情景:创建前文提到的Aggregate,创建其中的一个对象或者整个Aggregate,可能设计很多内部结构,设计的工作细节可能过于复杂。

辨析:对象的功能是体现在内部配置和与其他对象的关联这两个方面

原则:对象除了与自己的功能无关或者与关联交互角色无关的内容都要剔除掉

例子:1.汽车发动机是一个很复杂的机械,它本身就是很多零部件一起来履行发动机的功能--提供动力,可以让其他结构转动。2.我们可以设计一个发动机,自己有意识地挑选活塞并且组装到汽缸里面,火花塞也可以自己找到插孔把自己拧进去,这样多功能的发动机,可能不如现实世界的发动机可靠。3相反,我们采用其他东西来完成装配,可以是机器人,可以是人。4.装配的过程与对象的功能提供动力毫无关系。我们在生产汽车的时候需要装配,我们驾驶汽车不需要装配机器人或者装配工程师。5.因为装配和驾驶永远不会同时发生,所以才没有必要将两个功能合二为一。

提炼:发动机的装配和驾驶两个不会同时发生,所以才不需要两个功能合并到一个机制中

延申:更不能将装配过程的职责转移给用户,一个顾客自己去组装汽车发动机,简直可怕!

延申:复杂对象的创建是领域层的职责,不是那些表示领域模型的对象的职责

延申:有些时候,对象的创建和装配对应于领域中特定的重要事件,比如开立银行账户在金融领域非常重要。但是其他情况,对象的创建和装配在领域中毫无意义。这时候,我们就需要把这个变化的东西抽离出来,增加一个新的结构来控制这部分变化。

特点:它不对应于模型中的任何事物,但确实承担了领域层的部分职责。所以它确实属于领域设计的一部分

实现:除了编程语言中构建对象的方法的机制,我们需要更加抽象的而且不与其他对象发生耦合的构造机制--Factory。(与java,c++的构造函数不同)

类比:对象自己应该对外隐藏内部实现,提供公共方法来让客户使用;Factory封装了创建复杂对象或者Aggregate的所需要的复杂领域知识。它提供目标的接口和被创建对象的抽象视图。在Aggregate中,Factory是被作为一个整体来对待,并且确保它满足Aggegate前面提到的固定规则。

Factory的设计方式:有工厂方法Factory Method,抽象工厂Abstract Factory,构建器 Builder...

优秀标准:1.每个创建方法都是原子的,并且保证被创建的对象的固定规则或者Aggregate的固定规则。2.Factory 是抽象类,抽象为需要的类型。

例子:如果一个对象的创建是需要另外一个对象的信息,但是并不拥有所生成的对象。则可以在后者的对象上创建一个Factory Method,这样就不需要专门收集后者对象的信息来创建它,并且很清晰的表达出两者之间的关系

其他不需要使用Factory的情况:

其实就一个原则:它会让不具有多态性的对象复杂化,所以不需要Factory

1.类class是一个类型type,虽然对象也都可以看作类型;它还不是任何层次结构的一部分,也没有通过接口实现多态

2.客户关心的是实现,尤其是策略模式中的一部分

3.该对象没有其他嵌套对象,并且所有属性客户都可以访问,那就没必要,因为你没有封装任何东西

4.其他不复杂的情况

例子:java语言提供集合概念的时候,本身可以使用Factory,但是它是直接给了array,list ,map,set等具体的集合类,主要还是*选择具体的实现,应对不同的场景(Facotory 本身可以内置优化器),第二集合确实不多,就这几个。

Factory接口设计原则:

1.每个操作都是原子的。在一次交互将所有创建需要的参数全部传递给Factory,并且确定创建失败执行什么,可以抛出一个统一的编码标准来处理所有的Factory失败。

2.Factory将与参数发生耦合。此处提醒,参数若是本身就是其他复杂对象,就会引入复杂依赖。最安全的参数是来自于较低设计层的参数;另一个较好的参数是模型中与被创建对象密切关注的对象,这样大家的依赖一致。

Factory如何保证Aggregate的固定规则:具体情况具体分析,内部或者委托给被创建对象都可以

Factory控制唯一标识码:

唯一标识码可以程序生产,也可以外部提供。

外部提供,则需要作为参数传递进来;

程序提供,则需要内部处理;即使是数据库产生或其他方式,都必须让Factory知道并且控制,知道是怎样的标识,存放在哪里。

Factory 重建对象:

1.不分配新的唯一标识

2.当重建遇到问题,需要恢复时候。如果已经存在的,需要基于它来重建。

如果是数据库存储,那么借助ORM可以重建,如果是xml等形式,可以工具类重建,都可以封装在Factory中。

总结:Factory封装了对象创建和重建时候的生命周期转换

2.2 Repository

附赠:另一个对象与存储之间的生命周期转换就是Repository.

其实在学习程序的路上,这个Repository在一些程序框架上一直存在,可能有一些理解,没有过多去想。

情景:我们的很多数据都是在数据库的,数据库搜索是全局都可以访问的,那么我们就可以直接访问对象,不需要有关联关系,整个对象关系网也是在一个可控范围。是提供遍历还是使用搜索,这是一个设计决策。

提炼:在所有的持久化对象中,只有一小部分孤独的对象需要搜索,一般是ENtity,或者复杂的value Object,有时候就是字典枚举,其他对象就不适合使用搜索,因为需要严格树立二者之间的区别。

随意的数据库查询会破坏领域对象的封装和Aggregate.技术底层和数据库访问机制暴露会增加客户的复杂度,并且妨碍模型驱动设计。

为了控制数据库访问的复杂度,一般都是将sql封装到Query Object中,或者是使用metadata mapping layer。但是这样的话,我们就失去了领域模型,也失去了代码的业务语义表达力,只是数据库操作了

意义:Repository是概念框架,是把我们的角度拉回到模型上的优秀实践

概念:Repository实现了某项类型的对象为一个概念集合,行为类似于集合,只是多提供了一些复杂的查询功能而已。内部消化这些概念的创建和删除,不一定必须是数据库操作。

这个定义将一组紧密相关的职责集中在一起,这些职责提供了对于Aggregate根的整个生命周期的全程访问。

客户直接和这个Repository打交道。

Repository解决了客户的巨大负担,使得客户只是与一个简单,易于理解的接口进行对话,简洁可理解,就是软件设计的北极星目标

实践:只为那些确实需要直接访问Aggregate根的对象提供Repository,让客户始终聚焦于模型,对象的一切存储和访问全部交给Repository.

实践:除了这些模型,就是一些确实需要全局访问的孤独的对象,刚才提到的那些,就需要针对这些对象类型,每个都创建一个对象,提供一个清晰的全局可以访问的接口来访问,它内部的数据库操作,也是封装起来。这些对象在内存就是那些全局对象类型的替身。

Repository的查询:

如何设计这个查询接口有很多选择。

1.可以使用硬编码的形式来实现好一些特定参数的查询,形式各异。根据标识查询entity,根据属性值查询一个对象集合,根据值域选择对象,甚至可以执行Repository职责范围内的汇总计算啥的

这个形式在业务实际都是需要的,毕竟客户也需要做这些事情。

2.处理大量查询的时候,可以构建一个支持更加灵活的查询方式也是非常需要的

客户可以忽略Repository背后的实现,但是开发人会员必须非常清楚背后的实现

Repsository的实现:

根据使用的持久化技术和基础设施不同,Repository的实现形式也是千差万别。对于客户的接触而言,代码都是一样的,Repository将会委托相应的基础设施来完成背后的具体实现,这就是它的基本特征。

实现方法的注意原则:

1.对类型进行抽象:Repository含有特定类型的所有实例。但是不意味着所有的每个类都需要一个Repository.因为类型可以是一个层次结构的抽象超类,也可以是接口,不一定有层次结构,还可以是具体类。

2.充分利用与客户解耦的优点

测试时候,可以提供一个易于操作的,内存中的哑巴实现,方便客户代码和领域对象的测试

3.将事务的控制权留给客户。

如果Repository不参与事务控制,那么事务管理就会简单很多。虽然保存数据后立马提交是很自然的,但是你需要想到只有客户才有上下文,才能正确的初始化和提交工作单元。


辨析编程语言框架中Repository与领域设计的Repository的区别

很多编程框架中,已经提供了持久化对象的等效模式,也有可能定义了另外一种模式,当框架无法契合,要想办法在大方向上保持领域设计的基本原理,一些不符合的细节不需要过多苛求,寻求领域设计概念与框架概念的相似性。很多时候在java里面你会发现很少会使用实体bean,也就是Entity

结束!