领域驱动设计思路讲义ppt课件.ppt

上传人:牧羊曲112 文档编号:1370702 上传时间:2022-11-15 格式:PPT 页数:124 大小:3.58MB
返回 下载 相关 举报
领域驱动设计思路讲义ppt课件.ppt_第1页
第1页 / 共124页
领域驱动设计思路讲义ppt课件.ppt_第2页
第2页 / 共124页
领域驱动设计思路讲义ppt课件.ppt_第3页
第3页 / 共124页
领域驱动设计思路讲义ppt课件.ppt_第4页
第4页 / 共124页
领域驱动设计思路讲义ppt课件.ppt_第5页
第5页 / 共124页
点击查看更多>>
资源描述

《领域驱动设计思路讲义ppt课件.ppt》由会员分享,可在线阅读,更多相关《领域驱动设计思路讲义ppt课件.ppt(124页珍藏版)》请在三一办公上搜索。

1、领域驱动建模(Evans DDD),为什么使用MDD/DDD,MDD是模型驱动设计,与MDA模型驱动架构类似,体现为MDD = DDD + DSL。MDD is faster MDD很快(真正快速开发)在MDD应用模型中能够指定一个比传统的编程语言更高的抽象层次。该模型能够自动转化为可立即运行工作的应用程序(通过UML工具),生成可解释/执行模型代码。模型中的每个元素代表了多行代码,这样使得模型在一个更高的抽象层次比相应的代码要精简得多,更能大道至简。 MDD is more cost-effective MDD更具有成本优势因为MDD快速开发所以比较快地推向市场,MDD意味更少的人员 专家,

2、更高的质量,成本只是你学习MDD的成本以及工具采购,使用MDD拓展和维护应用程序的做法也更符合成本效益。通过更高级别模型阅读,比较容易理解应用程序。,MDD/DDD优点,MDD leads to increased quality MDD引导质量提高因为软件架构是在一个更高模型级别定义,然后通过引擎或框架转换成代码,这样我们能够让我们最优秀的人员从事引擎和框架的开发,进而提高平台的质量。 我们在项目中学习到的最佳实践模式可以被导引到引擎或框架代码产生器中,这样将被重用到所有使用MDD的项目中。MDD is less error-proneMDD很少出错测试会浪费很多时间,因为MDD可以确保代码

3、的质量,这样让你可以专注于测试应用程序的特点功能即验收测试上,不必关注通用的一些测试项目,如基础设施相关的技术问题或安全漏洞,MDD/DDD优点,MDD leads to meaningful validationMDD引导有意义的校验业务验证都已经在更高级别的模型中完成,编程工具只能帮助你验证代码语言的错误,不能验证业务上的错误。使用DSL能够在设计时候就执行一下,这样,错误信息也是 domain-specific级别的,可见Static Verification: An External DSL Advantage一文。MDD results in software being less

4、sensitive for changes in personnelMDD可以不在乎人事变动不需要特别的技术高手或架构师就能够建立软件,节约人力成本。此外,如果有人加入一个项目,比较容易理解高层次的软件应用模型,这比试图通过阅读理解源代码要轻松多)。,MDD/DDD优点,MDD empowers domain expertsMDD授权领域专家业务领域专家能够注重建模,而技术专家可以专注于建立一个MDD需要的框架或DSL等工具。一个软件系统不再只是程序编制就可以了,领域专家可以通过符号(如文字,图形,表格)等直接表达他们对业务领域的深入理解 。8. MDD let advanced progra

5、mmers focus on the hard stuff MDD能够让高级程序员专攻难关在MDD中,高级程序员的重复性工作要少得多,他们可以专注于他们工作的创造性方面。他们可以集中精力建设MDD的工具。他们还可以指导初级开发或领域的专家,也可以申请领取困难攻关,领域专家可以专注例如模型的图形用户界面,流程和业务规则。而应用程序的集成部分(使用webservices,API调用,数据库集成等)对于领域专家来说,存在太大困难,可以由高级程序员来完成。,MDD/DDD优点,MDD bridges the gap between business and IT MDD在业务和IT之间架设了桥梁领域专

6、家(或系统分析师)可以直接参与业务发展进程(见第7点)。技术专家(软件应用)可以在一个更高的层次定义尽可能和领域概念的定义声明有关模型。通过在模型和模型实现之间定义一个重要的转换机制,就可以在业务和IT技术之间建立一个桥梁,比如使用一个基于model-driven SOA的框架。 MDD results in software being less sensitive for changes in business requirements MDD可以减少业务需求带来改变的影响面对软件开发的问题之一是业务需求经常变化速度超过了软件系统支持改变,这对于企业是一个重点战略问题:能够保持足够长的时间

7、调整其核心业务与IT流程。MDD使软件开发更快,它也导致更容易改变应用。如果在业务需求和软件之间的存在一个联系,那么应用程序甚至有可能自动适应部分模型的变化。,MDD/DDD优点,MDD results in software being less sensitive for changes in technology MDD导致技术对变化不是太敏感技术变化很快, Java EE, SOA / SOBA, webservices,REST, SCA, OSGi等等,甚至迁移到云计算环境,当您希望您的应用程序迁移到其他技术时,MDD可以确保你不必改变你的应用模型,。唯一需要改变的代码生成器(或D

8、SL解释器)。改变后的代码生成器(或添加额外的代码生成选项)可以帮助所有的应用程序模型直接转换成基于新技术架构的代码。MDD really enforces architecture MDD增强架构当使用MDD开发软件应用程序,保证符合选定架构。你可以真正实现架构标准化, 构建架构原则Constructional architecture principles能够知道设计构建,并且能够反映在代码生成器或解释器中。,MDD/DDD优点,MDD captures domain knowledge MDD可以截获领域知识关于MDD的优点是,你不是只创建软件,但你也捕捉正式高层次的领域知识模型。在大多

9、数情况下这方面的知识是不明确,见文章基于DDD的DSL MDD provides up-to-date documentation MDD提供最新的文档当使用MDD,您不会遭遇不完整或过时的文件,因为该模型使用正确的抽象,可以让领域专家和客户都能看懂。MDD enables to focus on business problems instead of technology MDD能够关注业务而不是技术停止讨论使用WS-* 或者REST,MDD能够让你专注于业务问题如何解决,而不是着眼于如何解决这些问题的技术支撑。,Evans DDD,2004年Eric Evans 发表Domain-Dri

10、ven Design Tackling Complexity in the Heart of Software (领域驱动设计 )简称Evans DDD领域建模是一种艺术的技术,它是用来解决复杂软件快速应付变化的解决之道,Evans DDD,领域模型重要性,没有领域模型,只是靠代码编写完成一个又一个功能,复杂的领域需求会使得他们无法交流讨论,使工作陷入泥沼。有少许领域模型,但是没有维护好模型与代码直接的联系,两者产生差异,无法实现。,DDD优点,分析设计发展的三个阶段,第一阶段:围绕数据库的驱动设计,新项目总是从设计数据库及其字段开始。第二层次:面向对象的分析设计方法诞生后,有了专门的分析和设

11、计阶段之分,分析阶段和设计阶段是断裂的。第三阶段:融合了分析阶段和设计阶段的领域驱动设计(Evans: DDD)。,第一阶段:传统的数据库方式,过去软件系统分析设计总是从数据库开始,这种围绕数据库分析设计的缺点非常明显:1.分析方面:不能迅速有效全面分析需求。2. 设计方面:导致过程化设计编程,丧失了面向对象设计的优点。2. 运行方面:导致软件运行时负载集中在数据库端,系统性能难于扩展,闲置了中间件J2EE服务器处理性能。对象和关系数据库存在阻抗,本身是矛盾竞争的。,第二阶段:分析和设计分裂,第二阶段比第一阶段进步很多,开始采取面向对象的方法来分析设计需求。分析人员的职责:是负责从需求领域中收

12、集基本概念。面向需求。设计人员的职责:必须指明一组能北项目中适应编程工具构造的组件,这些组件必须能够在目标环境中有效执行,并能够正确解决应用程序出现的问题 两个阶段目标不一致,导致分裂,项目失败。,分析模型,分析模型会有知识不断消化的过程,但在编码时这些知识会被遗弃。开发人员被迫为设计进行新的抽象,那么分析人员嵌入模型中知识不能保留和重新发现。分析模型很多重要发现更改往往出现在设计实现过程,预先的模型可能深入研究无关主题,忽视重要主题。,问题,一个印在大纸张上的完整类图,整面墙都被它覆盖,花几个月分析开发的领域模型,模型大多数对象都与其中三四个对象有错综复杂的关系,且关系网几乎没有自然边界。分

13、析人员是忠于领域需求本质。问题:开发人员开始实现应用程序时,彼此纠缠的关系根本无法转换成可存储 可检索的实现。是不是基于概念的模型类图不能成为程序设计的基础?,DDD领域模型特点,统一领域模型:同时满足分析原型和软件设计 ,如果一个模型实现时不实用,重新寻找新模型。如果模型没有忠实表达领域关键概念时,也必须重新寻找新的模型。 统一语言:一个无处不在(ubiquitous )的语言,项目中所有人统一交流的语言。减少沟通疑惑,减少传达走样。使得软件更加适合需求。建模和设计成为单个迭代循环。将领域模型和设计紧密联系。因此,建模专家必须懂设计。,什么是DDD?,什么是领域模型 Domain Model

14、?,只有业务(真实世界的概念),没有技术。领域模型只表达需求真实世界模型,和软件架构技术无关。通过分层,将领域模型层突出,其他为辅助层。,机器人,机器人的领域模型,分层领域,将领域概念和其他软件技术相关概念分离,避免混淆,在系统庞大中失去对领域的把握。表现层:负责显示,解析用户命令应用层:指挥领域对象实现功能,必须保持简练,不是核心。领域层:核心 业务概念基础层:与软件技术相关。,航运程序,航运程序任务分解,表现层:接受用户输入(屏幕绘制 ) 向用户显示信息 (显示结果)领域层: 执行业务逻辑 (查询所有城市 城市和货物关联)基础层: 访问数据库(查询数据库 提交数据库更改网络通信) 不要将U

15、I 数据库和其他支持代码直接写到业务对象中,虽然简单容易,但是拓展困难,难于自动化测试。,分层 优点,每个层都是内聚的,并且只依赖它的下层,为了实现各层的最大解耦,IOC/DI容器是当前Java业务层的最好选择 。将所有与需求领域有关的代码集中在领域层,并与用户UI层 应用层和基础层分离。领域对象就可以重点关注表达领域模型,不关心自己的显示 存储和管理应用任务等事情。适合分布式部署,提升性能,高可伸缩性。,核心领域,大型系统中,有很多有用的组件,他们非常复杂,都是软件成功不可或缺的,这样组件实在太多,以至于领域模型的精髓部分变得不明显甚至被忽视。不可能所有部分都进行提炼,分清轻重缓急,让领域模

16、型真正成为资产。核心模型必须足够灵活和充分平衡来创建应用程序功能,不要倾向于使用技术基础结构如数据库来解决问题。无需专业业务知识容易能理解能引起程序员的兴趣,他们认为只有解决这些问题才能积累自己专业知识,同时为自己简历增光添彩,这对于公司是浪费。,不注重核心领域的案例,银团贷款系统:大多数技术天才和技术高手都对数据库映射层和消息接口津津乐道,而业务模型却交给一些刚刚涉足面向对象技术的新手们打理。尽管为持久领域对象提供详细注解文字说明,能够反映设计思路,也设计了友好的用户界面。这些特性都是外围,当这个软件最终交付用户使用时,差劲程序员二次开发拓展时却依然搞得一塌糊涂,整个项目差点失败。,浓缩模型

17、,找出核心模型,提供一种方法让我们很容易地从众多支持模型中将它区分出来,将最有价值 最体现专门知识的概念凸显出来,核心变小。让最好的程序员来处理核心模型,根据需要调整人员的配备,尽力找出核心的深层模型,对于其他部分投入必须经过考虑,是否能为提炼出来的核心提供支持。,通用子域,提炼核心领域,就必须剔除反面通用子域。不同行业运输业 银行业 制造业都需要某种形式组织结构图。组织结构图就是通用子域。许多应用跟踪应收帐款 费用分类和其他帐务信息,这些信息都可以使用通用的会计财务系统来处理。有两个项目处理带时区功能的日期和时间组件,花费最好的程序员数周时间,虽然必须做,但不是系统核心。考虑现有解决方案或开

18、源公开模型来替代通用子域。考虑外包,将通用子域外包,自己掌握核心领域。,内聚机制,算法或计算非常复杂,导致设计受到了冲击,模型中的概念变成了用“怎么做”来解释,而不是用“是什么”表达。设计中产生了一大堆用来实现算法 解决问题的方法,而描述这个问题的方法变得模糊不清。怎么做的方法在模型中泛滥成灾,表明模型存在某种问题。计算机制本身存在内聚性,我们计算方法不可能把所有功能都包含进来,我们需要的也不是一种万能计算机制。使用策略模式等框架把这些内聚计算分离出来,用一个明确接口来说明这个框架的功能,将怎么做复杂细节交给框架去完成。权限设计属于内聚机制,不是核心领域。使用RBAC,隔离核心,确定一个核心子

19、域把相关类转移到一个新的模块,并为模块指定一个与其概念相符的名称。重构代码,把那些不能直接表示该概念的数据和功能分开,并转移到其他模块。重构核心模块,使关联关系和交互作用更简单易懂。同时减少和其他模块的关系。对其他核心子域重复上面的步骤,直至得到隔离核心。,领域模型切割,1.将复杂大的领域分割成子领域。2.抓住子领域的核心,建立核心模型。3.对核心模型实现灵活性细节设计,旁门左道的快速开发,没有分层架构的快速开发基本是旁门左道,不如返回Foxpro和Delphi/VB两层时代。将本属于业务层的逻辑交由表现层来处理的快速UI方式也是一种旁门左道。快速开发必须基于良好的质量,虽然良好的分层架构带来

20、开发效率的降低,但是这些也是可以有方法解决。,模型元素,实体(Entity) A thread of continuity and identity.在时间上一系列连续性(continuity)和标识(identityID)来定义。值对象(Value Object):如果一个对象代表了领域的某种描述性特征,且没有概念性的标识。Description原型。服务(Service):行为接口。,实体,实体就是在客观世界中有实体内容的物体对象。经过时间延续一直保持其特点不变。软件实际是客观世界的拷贝或镜子,实体就是镜子中那个实物。必须拥有自己的唯一ID,主键,如果没有一个ID标识,为每个实例加上一个具

21、有唯一性ID,可能是内部使用。由于对象主观认定性,在特殊情况下,我们可能会主观划分一些实体。,实体建模,实体最基本职责是保证连续性,以便使之有清晰 可预见的行为。关注重点不是它们的属性或行为,而是找出固有的特征,提出其他细节。这个固有特征包括:可以唯一标识对象的 特征;经常用来查找或匹配对象的特征。只留下和特征相关的行为和属性,其他则转移到与该实体相关联的其他对象。目的:保持实体高度精简。,特征核心,实体标识,一个实体对应一个标识,处于分布式系统内存中,或保存在磁盘中,标识必须在整个系统唯一性。一个对象从数据库检索出来 生成一个新的对象实例,失去上次标识;通过网络传来一个对象实例,丢失原来的标

22、识;同一个对象存在多个不同副本,比如分布式缓存,标识混乱。怎样判断两个对象在概念上代表同一个实体呢?,实体如何标识,为每一个实例加上一个ID(一个数字或一个字符串)标记。标识具有不变性,与实体同生共死。对象实例被保存到数据库中时,ID值也要保存起来。ID可以由系统自动产生,ID产生算法必须保证系统中值的唯一性。,标识性质,隐式ID:软件用户通过人名查找客户,系统必须使用内部ID分辨两个姓名相同的联系人,用户可能看不到。显式ID:包裹传递服务,运输公司软件产生跟踪号码,预订机票酒店,产生确认码订单号。跨系统ID:两家医院的计算机系统对同一个患者生成不同的ID,使用公共机构如社保卡号用户自定义ID

23、:支票对账提供ID匹配,用用户来决定是否真的对应。,值对象,许多对象没有标识,只是事物的某些性质描述。四色原型中的蓝色des直接对应值对象。将所有对象都加上标识,会影响系统的性能,增加复杂性,使所有对象看上去都是一个模式,混乱。只关心what,不关心who 或 which,只关心对象是什么?如果有多个这样对象排列在一起,我们不用去分辨它们。只关心what:有两只相同颜色和粗细的笔,随便拿一个都可以画画。,地址值对象,邮购软件中的地址是值对象:用地址作为发货目的地。如果住在一起多个室友邮购,不影响邮递,有名字作为标识。邮政软件中的地址是实体:将地区分层次结构,区 城市 街道 邮编 个人地址。电力

24、运营软件中地址是实体:如果住在一起多个室友申请电力服务,电力公司必须区分。,值对象和实体是整体,值对象设计,由于不关心软件运行时使用的是值对象的哪个实例,没有了分辨拘束,可提升性能和优化。值对象复制性:两个人具有相同名字,表示名字的值对象可以互换复制,不会使他们成为一个人。值对象共享性:两个Person对象不需要自己各自的Name值对象,可以共用一个Name值对象。值对象不变性:值对象属于实体,当实体把它的值对象传递给其他对象时,如果其他对象对这个传过来的值对象修改不当,就会破坏其所有者的不变性约束,从而破坏它的所有者实体对象。,值对象共享,值对象非常巨大,每个电源插座都是一个值对象,一个房子

25、有上百个插座对象,由于值对象可以互换 共享,只使用一个插座实例就可以。Flyweight模式:避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类)。不适用于实体。,值对象复制,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节。Java的clone也是一种复制。复制产生大量对象会阻塞系统,但适合在分布式系统中,相反,使用共享,会降低性能;高并发系统中,复制减少锁处理,共享需要精妙的锁处理技巧。,实体和值对象区分,区分实体和值对象有助于我们在分析需求时抓住重点(实体),有主次之分,纲举目张。由于关注重点不同,就会对值对象定义不同。消费

26、拿起一瓶牛奶喝,这时我们关注的是他喝牛奶之外一些重点,至于选择哪个牛奶瓶不是我们关注重点,随便哪一个都行,这是值对象。对于牛奶生产商而言,每瓶牛奶都很重要,生产日期有效期等等,因此,这里牛奶瓶就成了实体。,帖子的值对象,Message是一个实体,有一个明显标识,每次都要区分是哪个帖子。帖子中标题 内容等是每个帖子都有的Description,属于值描述,因此有必要将标题 内容抽象出来作为值对象。,思考,论坛系统中,每个论坛有很多帖子,每个帖子后面有很多回复贴。两个主要类模型Forum和Messag。类似订单有两个Order和Product模型。是不是将Forum和Messag直接发生关联?根据

27、关联精炼限制原则,其实回复贴Message和Forum关联不是很紧,回复贴的聚合根应该是根贴(整体部分关系),存在精简余地。两个问题:哪个是实体和值对象?关联是否是聚合?,模型关联的实现,模型中关联在软件中都有一种机制对应。关联1.*和0.* Java 使用某种集合类 ,1.*”多重性,Java程序必须确保集合至少包含一项内容,聚合,代表一个类是另一个类的一部份这种关系。全体-部分 箭头由表示whole的类指向表示part的类 模型之间关联尽可能使用聚合表达,关联要点,关联要反映最本质的关系。如学生 课程 车和马达。关联不是手头任务本质或不能反映模型对象基本含义,完全取消它。现实世界中存在大量

28、双向多对多关联关系,双向关联和实现和维护带来很大困难,双向关联极少将联系本质体现出来,少用双向关联。尽可能约束关联,通过指定导航方向,减少不必要关联。模型中关联越少、越简单越好。,指定导航,限定导航 1:N 1:1,关联精炼 1:N关联,精炼后的1:1关联,高内聚低关联,高内聚低关联是设计基本原则。是重构的准则。找出关联是细分模型的一种方式,从而更恰当地定义模型边界。关联不是手头任务本质或不能反映模型对象基本含义,完全取消它。少用双向关联,除非技术性能要求。模型中关联越少、越简单越好。完全摆脱数据库影子,SQL语句作为规则封装在模型中。,再思考,Forum直接和Message发生关联不够精细,

29、回复贴就和Forum没有聚合,不是整体部分关系,它的整体聚合根是根贴,根贴可直接和Message发生关联。将Message使用抽象继承建模,落实为两个子类:根贴和回复贴。Forum不能直接和Message继承模型耦合,否则继承模型一旦变动,影响Forum。引入集合容器概念Thread,一个根贴和它一系列回帖组成一个Thread。,增加Thread,Thread是实体还是值对象?,实体和值对象主要区别是否有标识ID,这个ID不是数据表ID,而是一个更高抽象概念,代表是否需要分辨它的意思。区分实体和值对象主要作用是主次之分,如果你认为它很重要,它可以从此要的值对象上升为实体。用户找到Forum后,

30、是否需要根据Thread再找Message?如果是,Thread应该是实体;如果不是,Thread可以隐藏在根贴Message后面做值对象。,鸡头 or 凤尾?,由于当初引入Thread的目的是让Forum不直接和Message复杂内部树形结构发生耦合,所以,Thread必须发挥这个中间者的作用。Thread应该被看成实体。如果Thread是值对象,Thread作为帖子集合容器与外界松耦合的作用就失去,同理可以推广到:Order - OrderLine - Product,领域对象生命周期,重视生命周期,对一个对象修改操作,是否会引发对象关联网中其他对象的变化?多个客户端并发访问一个对象,必须

31、提供保护如锁操作,防止相互依赖的对象被同时修改。如果保护范围错了,后果很严重。如何在性能和事务上做到平衡,最好解决方式是对领域理解深刻,了解某些类的实例修改频率如何?让它远离高并发访问,串行化与并行计算。数据库事务ACID事务棘手实际是我们的模型不清晰。,聚合Aggregate,一个聚合是一簇相关联的对象,出于数据变化的目的,将这些对象视为一个单元。每个聚合都有一个根和一个边界。边界定义了聚合中应该包含什么。根是包含在聚合中的单个特定实体。根是聚合中唯一允许被外部引用的元素,在聚合边界内,对象之间可以相互引用。实际就是整体和部分的关系。,轿车根,聚合中的不变性,不变性Invariants定义:

32、无论何时数据发生变化,都必须满足所有一致变化的规则 ,俗话:同生死。聚合内部的不变量必须在每次事务完成时满足。这可有仓储来实现。 一些依赖关系只能在某些特定的时刻 通过事件借助有事务支持的服务来完成,或通过线程安全模式实现原子操作。,如何做到不变性,根实体具有全局标识,并最终负责对不变量的检查。根实体有全局标识,而边界之内实体有本地标识,这些标识仅在聚合内部是唯一的。聚合边界外任何对象除了可以引用根实体,不能持有任何对其内部对象的引用。根实体可以把内部其他实体引用传递给其他对象,只能临时使用。根实体可以复制内部一个值对象实例副本给外部另外一个对象,副本再也与聚合无关系了。,CRUD中不变性约束

33、,通过数据库查询直接获得的对象只有聚合的根,其他所有聚合内对象可以通过聚合关系找到,性能上可采取懒加载防止大对象。删除必须一次性删除聚合边界内所有对象。当在聚合边界内发生的任何对象修改被提交时,整个聚合的所有不变量必须被满足,也就是统一修改。,聚合根和不变性,采购订单,订单不变量约束,所以采购单项的金额之和不得超过采购单的最高限额。不变量保证:当加入新子项时,PO对总金额检查,如果不对,把自己标记非法,不好。变更管理:删除PO时,子项同时删除,但是它们关联关系何时终止,模型没有指示。不同时间修改商品价格会造成哪些影响无法评估。并发共享:如何解决多个用户同时修改一个PO?,并发锁粒度,如果多个用

34、户同时修改一个PO,我们必须对这个PO实例锁定,以让某个时刻只能一个用户修改。通过数据库锁机制或者使用线程锁机制实现,关键是锁PO整个实例带来问题,这种锁排他性的,就无法允许其他用户也许对PO其他部分进行访问,性能差。更改模型,根据修改频繁程度单独列出一个对象,比如Price经常修改,就成为Price对象,锁定Price这个小对象,无需锁定整个PO。,新订单模型,案例 找出聚合根,在初步确定了Forum Thread和Message模型类之后,我们需要对这些模型内部进行细化。在细化类字段和关联关系时,我们要遵守找出高聚合根这个宗旨。根贴的一系列回帖,组成聚合的边界,那么谁是聚合根实体?Root

35、Message好像是聚合根,但是因为Thread是整个聚合体的实体代表,用Thread作为聚合根比RootMessage更确切,Thread类Car。,案例 划出不变性,在考察Forum的各个字段时,有几个字段是可能因为用户发帖而经常频繁变化,表示当前论坛状态的字段:最新贴lastPost 帖子总数ThreadCount等。lastPost和ThreadCount不同name等其他本质固有属性,他们修改变化的周期和name等是不一致的,发一个新贴不代表就要修改论坛名称,但是要修改lastPost和ThreadCount。创建一个ForumState类来封装变化的lastPost和ThreadC

36、ount。,可变部分,聚合根和不变性,论坛的不变性划分,不变性的实现方式,在生命周期中维护对象的完整性。避免模型由于管理生命周期的复杂性而陷入困境。三个模式来处理: 1. 聚合(Aggregate):定义清晰的所有权和边界使模型更加紧凑,避免出现盘根错节的对象关系网。聚合圈出一个范围,在这个范围中,对象无论在哪个生命周期,保持不变性。 2. 工厂(Factory) 3. 组合(Respository) 生命周期之始,使用工厂和组合提供了访问和控制模型对象的方法,生命周期边界和管理,聚合圈出一个范围如前图中红线,在这个范围中,对象无论在哪个生命周期,保持不变性。也就是子对象和父对象的生命周期是一

37、致不变的。建立聚合的模型,并且把工厂和组合加入设计中来,可以使我们系统地对模型对象生命周期进行管理。生命周期之始,使用工厂和Repository提供了访问和控制模型对象的方法。,工厂,生命周期管理具有复杂的职责,如果让一个复杂对象来负责自身的创建工作,会由于职责过载产生问题,人不能拎着自己头发拔高,孙猴子也是从石头缝里出来的,不是从自己身体钻出来的。复杂对象的创建和组装应该由单独工厂实现,也就是工厂模式。将对象创建和使用分离。工厂属于领域层,工厂把聚合作为一个整体创建出来,创建方法必须是原子的,保证其不变量得到满足。,对象内工厂,在一个领域对象内,如果希望向一个已有的聚合中加入元素,可以在聚合

38、根中创建一个工厂方法,既隐把聚合内部实现向外部客户隐藏起来,聚合根保证在元素加入后的完整性。,Thread内工厂,前面讨论了Thread作为一系列帖子的聚合根,那么如果增加新的帖子,通过Thread内置的工厂来实现, synchronized保证原子性:public synchronized void addNewMessage(.) state.addMessageCount(); state.getTreeModel().addChild(.); state.setLastPost(forumMessageReply); state.setModifiedDate(forumMessage

39、Reply.getModifiedDate(); forum.addNewMessage(forumMessageReply);,专门工厂创建聚合,如果聚合根需要一个工厂创建,又不适合充当工厂,也就是没有一个自然地方容纳工厂,那么就创建一个专门的工厂对象或服务。,实体和值对象工厂,值对象具有不变性,一旦声场创建就是最终状态,工厂必须将其完整的创建出来。实体工厂只要求构造一个有效聚合所需要的那些本质属性即可,注意是本质固有属性,只要保证不变性就可以,其他不变性没有要求的可在以后追加。,存储对象重建,内存是对象的正常活动空间,但是对象通常要保存到数据库或网络输出,会将对象压扁,需要一个复杂过程才能

40、把压扁各个部分恢复为活的正常对象。用工厂重建对象,标识ID必须成为输入参数。从数据库中重建对象保持不变性难度加大。,Repository由来,数据库只是对象的永久保存方式,就象我们打字时经常需要存盘一样,我们不能因为要“存盘”而去关心“存盘文件格式(数据表结构)”。我们应该更聚焦在模型这个对象,把所有对象的保存(冬眠)和调用(激活)交由Respository完成。对象保存到数据库交由专门的Repository仓储来完成,由Repository负责如何将对象分解成数据库能够保存的格式。,JiveJdon的工厂,Thread和RootMessage实体模型对象的获得都是从专门的ThreadDire

41、ctor和ThreadBuilder 创建出来的 。(讲解具体代码)如果使用Hibernate这样O/R Mapping框架 ,由Hibernate完成对象创建和组合。无论什么实现方式,从持久层输出到业务层的都必须是对象,这是OO设计基本原则 ,可以保证在业务层不能访问到数据表,只是对象。,创建原子性,每个操作必须是原子性的,要么全部完成,要么一个都不完成。public ForumThread getThread() throws Exception ForumThread forumThread = (ForumThread) forumThreadBuilder.create(thread

42、Id); if (forumThread = null) return null; if (forumThread.isEmbedded()/确保构建是一次性 return forumThread; /开始构建方法,这里开始必须是原子性 construct(forumThread, rootForumMessage, forum); forumThread.setEmbedded(true); return forumThread;思考:如果有多余两个用户以上开始执行构建方法,如何保证他们构建出来的Thread是同一个呢?,如何确保原子性,如果在构建方法这里加入synchronized,将导致

43、构建所有Thread都只有一个线程,这不是我们本意,我们本意是构建某个Thread实例时,只能有一个线程。否则导致性能降低,多用户系统变成单用户系统。forumThreadBuilder.create(threadId);实际是从缓存中获取一个Thread(缓存中没有就从数据库构建),而在缓存操作某个Thread时,是一个同步操作,这样将锁粒度降低到最接近要求。,Thread构建,在Thread的construct方法中,我们必须构建聚合边界元素。Thread为聚合根,其下有RootMessage以及一系列回帖,如何表达这种聚合结构呢?帖子与回帖之间结构是一种树形结构,我们可以用树形结构来表达

44、。建立一个TreeModel对象,封装了帖子之间的父子结构,当然只是ID,不是完整帖子。,Thread构建代码,/构建Thread自身固有本质属性 名称等forumThreadBuilder.buildProperties(forumThread);/构建聚合边界,树形结构forumThreadBuilder.buildTreeModel(forumThread);/建立真正树形结构根元素根贴forumThreadBuilder.buildRootMessage();/建立与Forum的关联。forumThreadBuilder.buildForum(.);/建立Thread状态,状态是对象本

45、质属性,有对象就有状态。forumThreadBuilder.buildState(.);,对象和数据库,数据库查询是全局的,可以直接想得到想要的对象,但对象内部的聚合不变性就会被破坏。对象网是通过对象关联来管理的,但我们不需要把所有对象连接起来。是使用导航限制连接,然后先找出聚合根,再根据根的限制导航找出结果?还是使用查询,直接查询我们要得到的结果?Customer与Order,是设计为关联,还是通过CustomerId把Order从数据库中查询筛选出来?,数据库编程思路,很多开发人员更喜欢使用SQL,对如何从数据库抓取他们想要的对象感兴。业务逻辑都跑到SQL代码或客户端代码中去,而实体和值

46、对象变成了纯粹数据容器。大部分数据库访问计数复杂性,如数据库范式,很快使得代码陷入混乱,只好抛开领域层,模型成了摆设。另外一个极端:加入太多关联,成了蜘蛛网,把模型搞得一团糟。,Repository和查询,不需要为通过导航方法能够获得持久对象提供查询访问,聚合内部对象可以通过根来导航。值对象无需全局查询获得,比较少见,值对象生命周期很短,属于临时对象,一般通过聚合根获得。仓储可以实现数据库新增 修改 删除和查询CRUD,仓储可以实现不同标准的各种查询。,Repository定义,为客户端访问某一聚合类对象提供全局访问接口Repository , Repository主要是在内存中建立一系列的聚

47、合对象。Repository提供CRUD等通用对象操作方法,把数据库数据存储插入和删除等方法封装起来。但是不插手事务。提供符合对象方式筛选查询,仅为确实需要直接访问的聚合根提供仓储,其他则不必。让客户端只关心模型,而不是数据存储。存储和访问都交给Repository完成。,查询仓储Specification,查询存在大量项目,但必须是围绕实体聚合根对象的查询,可以使用专门框架。引入Specification制定,用来让客户端将它希望获得的查询结果描述出来,也就是制定出来。输入参数使用Criteria来封装各种查询输入参数,提供Criteria的查询框架比较复杂,如Hibernate的Crite

48、ria。如果聚合中有大数据,可通过懒加载lazy延迟加载,只返回一个代理,使用时,再进行加载。,CRUDTemplate,很多框架都有CRUD模板,在这个模板中,我们使用通用的object来替代领域对象。客户端只要继承这个模板,然后实现特殊多样的查询方法即可。public void insert(Object o) throws Exception daoCRUD.insert(o);clearCacheOfItem();public void update(Object o) throws Exception daoCRUD.update(o);clearCacheOfItem();publ

49、ic void delete(Object o) throws Exception daoCRUD.delete(o);clearCacheOfItem();public Object loadModelById(Class classz, Serializable Id) throws Exception return daoCRUD.loadById(classz, Id);,Repository和工厂,区分新创建和构建,第三种模型 服务,将行为放入对象才是真正对象,这样可防止面向过程编程。但是放入不确当行为,会破坏对象的清晰概念。有些对象侧重于操作动作行为,没有状态,就象四色图中MI,是

50、一种活动事件,这些对象应该被标记为服务。服务来源现实:加油服务 快递服务,服务不是一个过程化编程的概念。分应用服务(事务安全)和领域服务。,服务的难点,服务因为处于过程化编程功能类和OO之间,很容易被误用。误用情况就是:将业务功能使用服务实现,这就造成了贫血失血模型。如果将所有功能都塞入实体对象,那么实体对象变得非常庞大臃肿,失去灵活性。对行为方法划分粒度很重要:属于实体本质行为的放入实体;而这个行为跨越了实体本身范围和生命周期的,类似管理协调行为的,可放入服务中。,业务逻辑和服务,不要将业务逻辑写到Service服务,这样会造成模型失血,只有属性没有业务行为。将业务行为放入领域层,不一定放在

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号