领域驱动设计
2023-08-23 09:40:38 0 举报
领域驱动设计是一种由域模型来驱动着系统设计的思想,不是通过存储数据词典(DB表字段、ES Mapper字段等等)来驱动系统设计。领域模型是对业务模型的抽象,DDD是把业务模型翻译成系统架构设计的一种方式。
作者其他创作
大纲/内容
领域模型特点
领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;
领域模型是有边界的,只反映了我们在领域内所关注的部分。
领域模型是有边界的,只反映了我们在领域内所关注的部分。
领域模型只反映业务,和任何技术实现无关,领域模型不仅能反映领域中一些实体概念,如货物,书本等还能
反应领域中的一些过程概念,如资金转账等;
反应领域中的一些过程概念,如资金转账等;
领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,
业务可理解性以及可重用性等方面都有帮助。
业务可理解性以及可重用性等方面都有帮助。
领域模型能够帮助开发人员相对平滑的将领域知识转化为软件构造
领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过
领域模型进行交流,彼此共享知识与信息、因此大家面向的都是同一个模型,所以可以防止需求走样。
可以让软件设计开发人员做出来的软件真正满足需求
领域模型进行交流,彼此共享知识与信息、因此大家面向的都是同一个模型,所以可以防止需求走样。
可以让软件设计开发人员做出来的软件真正满足需求
要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通努力,然后才能使
大家对领域的认识不断深入、从而不断细化和完善领域模型
大家对领域的认识不断深入、从而不断细化和完善领域模型
为了让领域模型看得见,我们需要用一些方法来表示他,图是表达领域模型最常用的方式,但不是唯一的
表达方式,代码或文字描述也能表达领域模型。
表达方式,代码或文字描述也能表达领域模型。
领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型
能够更快速的响应需求变化。
能够更快速的响应需求变化。
领域通用语言
核心原则:使用一种基于模型的语言,要求团队在进行所有的交流都使用一致的语言,
确保团队使用的语言在所有的交流形式中看上去都是一致的,这种语言被称为“通用语言”
确保团队使用的语言在所有的交流形式中看上去都是一致的,这种语言被称为“通用语言”
将领域模型转换为代
码实现的最佳实践
码实现的最佳实践
问题:拥有一个看上去争取的模型不代表模型能被直接转换成代码,也或者他的实现可能会违背我们所不建议的软件设计原则,
我们该如何实现从模型到代码的转换,并让代码具有可扩展性、可维护性、高性能,另外,如实反映领域的模型可能会导致对象
持久化的一系列问题,或者导致不可接受的性能问题。
我们该如何实现从模型到代码的转换,并让代码具有可扩展性、可维护性、高性能,另外,如实反映领域的模型可能会导致对象
持久化的一系列问题,或者导致不可接受的性能问题。
方法:应该紧密关联领域建模和设计,紧密将领域模型和软件编码实现捆绑在一起,模型在构建时就考虑到软件和设计,
开发人员会被加入到建模的过程中,主要的想法是选择一个能够恰当在软件中表现的模型,这样设计过程会很舒畅并且基于模型,
代码和其下的模型紧密关联会让代码更有意义并于模型更相关,有了开发人员的参与就会有反馈。能保证模型被实现成软件,
如果其中某处有错误,会在早期就被标识,问题也会容易修正,写代码的人会很好的了解模型,会感觉自己有责任保持他的完整性,
他们会意识到代码的一个变更其实就隐含着对模型的变更,另外,如果哪里的代码不能表现原始模型的话,他们会重构代码,如果分析人员
从实现过程中分离出去,他会不再关心开发过程中引入的局限性,最终结果是模型不在使用,任何技术人员想对模型做出贡献必须花费一些
时间来接触代码,无论在项目中担负的是什么主要角色,任何一个负责修改代码的人都必须学会用代码表现模型。每位开发人员都必
须参与到一定级别的领域讨论中并和领域专家联络。(重在参与)
开发人员会被加入到建模的过程中,主要的想法是选择一个能够恰当在软件中表现的模型,这样设计过程会很舒畅并且基于模型,
代码和其下的模型紧密关联会让代码更有意义并于模型更相关,有了开发人员的参与就会有反馈。能保证模型被实现成软件,
如果其中某处有错误,会在早期就被标识,问题也会容易修正,写代码的人会很好的了解模型,会感觉自己有责任保持他的完整性,
他们会意识到代码的一个变更其实就隐含着对模型的变更,另外,如果哪里的代码不能表现原始模型的话,他们会重构代码,如果分析人员
从实现过程中分离出去,他会不再关心开发过程中引入的局限性,最终结果是模型不在使用,任何技术人员想对模型做出贡献必须花费一些
时间来接触代码,无论在项目中担负的是什么主要角色,任何一个负责修改代码的人都必须学会用代码表现模型。每位开发人员都必
须参与到一定级别的领域讨论中并和领域专家联络。(重在参与)
领域建模时思考问题的角度
用户需求不等于用户,捕捉用户心中的模型不能等同于以用户为核心设计领域模型
设计领域模型时不能以用户为中心作为出发点去思考问题,不能老想着用户会对系统做什么;
而应该从一个客观的角度,根据用户需求挖掘出领域内相关的事物,思考这些事物的本质关联及其变化规律作为出发点
去思考问题。
而应该从一个客观的角度,根据用户需求挖掘出领域内相关的事物,思考这些事物的本质关联及其变化规律作为出发点
去思考问题。
领域模型是排除了人之外的客观世界模型,但是领域模型包含人所扮演的参与者角色,但是一般情况下不要让参与者角色在领域模型中占据主要位置,
如果以人所扮演的参与者角色在领域模型中占据主要位置,那么各个系统的领域模型将变得没有差别,因为软件系统就是一个人机交互的系统,都是以人为主的活动记录或跟踪;比如:论坛中如果以人为主导,那么领域模型就是:人发帖,人回帖,人结贴,等等;DDD的例子中,如果是以人为中心的话,就变成了:托运人托运货物,收货人收货物,付款人付款,等等;因此,当我们谈及领域模型时,已经默认把人的因素排除开了,因为领域只有对人来说才有意义,人是在领域范围之外的,如果人也划入领域,领域模型将很难保持客观性。领域模型是与谁用和怎样用是无关的客观模型。归纳起来说就是,领域建模是建立虚拟模型让我们现实的人使用,而不是建立虚拟空间,去模仿现实。
如果以人所扮演的参与者角色在领域模型中占据主要位置,那么各个系统的领域模型将变得没有差别,因为软件系统就是一个人机交互的系统,都是以人为主的活动记录或跟踪;比如:论坛中如果以人为主导,那么领域模型就是:人发帖,人回帖,人结贴,等等;DDD的例子中,如果是以人为中心的话,就变成了:托运人托运货物,收货人收货物,付款人付款,等等;因此,当我们谈及领域模型时,已经默认把人的因素排除开了,因为领域只有对人来说才有意义,人是在领域范围之外的,如果人也划入领域,领域模型将很难保持客观性。领域模型是与谁用和怎样用是无关的客观模型。归纳起来说就是,领域建模是建立虚拟模型让我们现实的人使用,而不是建立虚拟空间,去模仿现实。
领域驱动设计的经典分层架构
用户界面/展现层:负责向用户展示信
息以及解释用户命令
息以及解释用户命令
请求应用层以获取用户所需要展现的数据
发送命令给应用层要求其执行某个用户命令
应用层:很薄的一层,定义软件要完成的所有任务,对外为展现层提供各种应用功能(包括查询或命令),
对内调用领域层(领域对象或领域服务)完成各种业务逻辑,应用层不包含业务逻辑。
对内调用领域层(领域对象或领域服务)完成各种业务逻辑,应用层不包含业务逻辑。
领域层:负责表达业务概念,业务状态信息以及业务规则,领域模型处于这一层,是业务软件的核心。
基础设施层:本层为其他层提供通用的技术能力;提供了层间的通信;为领域层实现持久化机制;总之基础设施层
可以通过架构或框架来支持其他层的技术要求。
可以通过架构或框架来支持其他层的技术要求。
领域设计过程中使用的模式
关联的设计
关联尽量少,对象之间复杂的关联容易形成对象关系网,这样对于我们理解
和维护单个对象很不利,同时也很难划分对象和对象之间的边界;另外,同时减少
关联有助于简化对象之间的遍历。
和维护单个对象很不利,同时也很难划分对象和对象之间的边界;另外,同时减少
关联有助于简化对象之间的遍历。
过多的关联也许在业务上是自然的,通常我们会用一个集合来表示1对多的关系,但我们往往
也需要考虑到性能问题,尤其是当集合内元素非常多的时候,此时往往需要通过单独查询来
获取关联的集合信息。
也需要考虑到性能问题,尤其是当集合内元素非常多的时候,此时往往需要通过单独查询来
获取关联的集合信息。
关联尽量保持单向的关联
在建立关联时,我们需要深入去挖掘是否存在关联的限制条件,如果存在,那么最好把这个限制条件
加到这个关联上,往往这样的限制条件能将关联简单化,即可以将多对多简化为1对多。
加到这个关联上,往往这样的限制条件能将关联简单化,即可以将多对多简化为1对多。
实体
实体是领域中需要唯一标识的领域概念
区分两个实体,如果唯一标识不一样,那么即便实体的其他所有
属性都一样,也认为他们是两个不同的实体。
属性都一样,也认为他们是两个不同的实体。
实体有生命周期,可能会被持久化到数据库,因此,不为实体定义一种可以唯一区分的标识,就无法区分到底哪个是实体
不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或者行为转移到其他关联的实体
或值对象上。如:Customer实体,他有一些地址信息,由于地址信息是一个完整的有业务含义的概念,所以,我们可以定义
一个Address对象,然后把Customer的地址相关的信息转移到Address对象上。如果没有Address对象,而把这些地址信息直接
放在Customer对象上,并且如果对于一些其他的类似Address的信息也都直接放在Customer上,会导致Customer对象很混乱,
结构不清晰,最终导致它难以维护和理解
或值对象上。如:Customer实体,他有一些地址信息,由于地址信息是一个完整的有业务含义的概念,所以,我们可以定义
一个Address对象,然后把Customer的地址相关的信息转移到Address对象上。如果没有Address对象,而把这些地址信息直接
放在Customer对象上,并且如果对于一些其他的类似Address的信息也都直接放在Customer上,会导致Customer对象很混乱,
结构不清晰,最终导致它难以维护和理解
值对象
领域中,并不是每个事物都必须有一个唯一标识
如果两个对象的所有属性的值都相同的我们认为他们是同一个对象的话,就可以把这种对象设计为
值对象。
值对象。
值对象另一个明显的特征是不可变,所有属性都是只读的
应该给值对象设计的尽量简单,不要引用很多其他对象
当你要修改Customer的Address对象引用时,不是通过Customer.Address.Street这样
的方式来实现,因为值对象是只读的,它是一个完整的不可分割的整体。我们可以这样做
Customer.Address = new Address(…);
的方式来实现,因为值对象是只读的,它是一个完整的不可分割的整体。我们可以这样做
Customer.Address = new Address(…);
领域服务
领域中的一些概念不适合建模为对象,即归类到实体对象或值对象,因为他们本质上就是些操作,而不是事物
这些操作往往涉及多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作。
如果强行将操作分配给任何一个对象,则被分配的对象就是承担一些不该承担的职责,从而导致对象的职责不明确
基于类的面向对象语言规定任何属性或行为都必须放在对象里面。所以我们需要寻找一种新的模式来表示这种
跨多个对象的操作
跨多个对象的操作
DDD认为服务是一个很自然的范式用来对应这种跨多个对象的操作,所以就有了领域服务模式,
和领域对象不同,领域服务是以动词开头来命名,如资金转帐服务可以命名为MoneyTransferService
和领域对象不同,领域服务是以动词开头来命名,如资金转帐服务可以命名为MoneyTransferService
也可以把服务理解为对象,但这和一般意义上的对象有区别,一般的领域对象都是有状态和行为的,而领域服务没有
状态只有行为,领域服务是无状态的,存在的意义就是协调领域对象共同完成某个操作。所有状态都保存在
相应的领域对象中。
状态只有行为,领域服务是无状态的,存在的意义就是协调领域对象共同完成某个操作。所有状态都保存在
相应的领域对象中。
模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,
模型关注领域的静态结构,场景关注领域的动态功能,这也符合现实中出现的各种现象,有动有静,有独立又协作。
模型关注领域的静态结构,场景关注领域的动态功能,这也符合现实中出现的各种现象,有动有静,有独立又协作。
领域服务另一个重要功能:避免领域逻辑泄漏到应用层。因为如果没有领域服务,那么应用层会直接调用领域对象
完成本该属于领域服务该做的操作,这样以来,领域层可能会把一部分领域知识泄漏到应用层,因为应用层需要了解每个
领域对象的业务功能,具有哪些信息,以及可能会与哪些领域对象交互,因此,引入领域服务可以有效防止领域层的逻辑泄漏到
应用层,对于应用层来说,从可理解的角度来讲,通过调用领域服务提供的简单易懂但意义明确的接口肯定比直接操作领域对象
容易。这里也可以看到领域服务具有Facade功能。
完成本该属于领域服务该做的操作,这样以来,领域层可能会把一部分领域知识泄漏到应用层,因为应用层需要了解每个
领域对象的业务功能,具有哪些信息,以及可能会与哪些领域对象交互,因此,引入领域服务可以有效防止领域层的逻辑泄漏到
应用层,对于应用层来说,从可理解的角度来讲,通过调用领域服务提供的简单易懂但意义明确的接口肯定比直接操作领域对象
容易。这里也可以看到领域服务具有Facade功能。
软件中一般有三种服务:
应用层服务,领域服务,基础服务
应用层服务,领域服务,基础服务
应用层服务
获取输入如XML请求
发送消息给领域层服务,要求其实现转账的业务逻辑
领域层服务处理成功,调用基础层服务发送Email通知
领域层服务
获取源账号和目标账号,分别通知源账号和目标账号进行扣除金额和增加
金额的操作。
金额的操作。
提供返回结果给应用层
基础层服务
按照应用层请求,发送Email通知
聚合以及聚合根
(Aggregate和Aggregate Root)
(Aggregate和Aggregate Root)
聚合,通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂
的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们
把聚合看作是一个修改数据的单元。
的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们
把聚合看作是一个修改数据的单元。
聚合特点
每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或者值对象,
根是聚合内的某个实体
根是聚合内的某个实体
聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,
必须通过聚合根开始导航,绝不能绕过聚合根直接访问聚合内的对象,也就是聚合根是外部可以
保持对他引用的唯一元素。
必须通过聚合根开始导航,绝不能绕过聚合根直接访问聚合内的对象,也就是聚合根是外部可以
保持对他引用的唯一元素。
聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为他们总是从属于这个
聚合的。
聚合的。
聚合根负责与外部其他对象打交道并维护自己内部的业务规则
基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,
也就是说我们不能直接查询聚合内部的某个非根的对象
也就是说我们不能直接查询聚合内部的某个非根的对象
聚合内部的对象可以保持对其他聚合根的引用
删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都属于一个聚合,是一个完整的概念
如何识别聚合以及聚合根
分析对象哪些有独立存在的意义,即他不依赖于其他对象的存在
可以被独立访问,还是必须通过某个其他对象导航得到
如何识别聚合:从业务角度深入分析哪些对象关系是内聚的,即我们会把他们看成一个整体来考虑,这些对象就可以把他们放在一个聚合内;
所谓关系是内聚的,指这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。当我们在修改一个聚合时,
我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则,作为一条建议,聚合不要太大,否则即便能够做到在事务级别保持聚合的业务规则
完整性,也可能会带来一定的性能问题。在大部分领域模型中,70%的聚合通常只有一个实体,即聚合根,该实体内部没有其他实体,只包含一些值对象,
另外30%的聚合中,也只包含两到三个实体,意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。
所谓关系是内聚的,指这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。当我们在修改一个聚合时,
我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则,作为一条建议,聚合不要太大,否则即便能够做到在事务级别保持聚合的业务规则
完整性,也可能会带来一定的性能问题。在大部分领域模型中,70%的聚合通常只有一个实体,即聚合根,该实体内部没有其他实体,只包含一些值对象,
另外30%的聚合中,也只包含两到三个实体,意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。
如何识别聚合根:如果一个聚合只有一个实体,那么这个实体就是聚合根,如果有多个实体,那么可以思考聚合内哪些对象有独立存在的意义
并且可以和外部直接进行交互
并且可以和外部直接进行交互
工厂: 工厂也是一种体现封装思想的模式,有时创建一个领域对象是一件比较复杂的事情,不仅仅是简单的new操作,正如对象封装了内部实现一样,
工厂是用来封装创建一个复杂对象尤其是聚合时所需的知识。工厂的作用就是将创建对象的细节隐藏起来,客户传递给工厂一些简单的参数,然后工厂可以在
内部创建出一个复杂的领域对象传递给客户。领域模型中其他元素都不适合做这个事,所以需要引入这个新的模式,工厂。工厂在创建一个复杂的领域对象时,通常会知道满足什么业务规则(它知道先怎样实例化一个对象,然后在对这个对象做哪些初始化操作,这些知识就是创建对象的细节),如果传递进来的
参数符合创建对象的业务规则,则可以顺利创建相应的对象;但是如果由于参数无效等原因不能创建出希望的对象时,应该抛出一个异常,以确保不会创建出错误的对象,当然我们也不总是需要通过工厂来创建对象,事实上大部分情况下领域对象的创建都不会太复杂,隐藏创建对象的好处是显而易见的,这样可以不会让领域层的业务逻辑泄漏到应用层,同时减轻了应用层的负担,只需要简单的调用领域工厂创建出期望的对象即可。
工厂是用来封装创建一个复杂对象尤其是聚合时所需的知识。工厂的作用就是将创建对象的细节隐藏起来,客户传递给工厂一些简单的参数,然后工厂可以在
内部创建出一个复杂的领域对象传递给客户。领域模型中其他元素都不适合做这个事,所以需要引入这个新的模式,工厂。工厂在创建一个复杂的领域对象时,通常会知道满足什么业务规则(它知道先怎样实例化一个对象,然后在对这个对象做哪些初始化操作,这些知识就是创建对象的细节),如果传递进来的
参数符合创建对象的业务规则,则可以顺利创建相应的对象;但是如果由于参数无效等原因不能创建出希望的对象时,应该抛出一个异常,以确保不会创建出错误的对象,当然我们也不总是需要通过工厂来创建对象,事实上大部分情况下领域对象的创建都不会太复杂,隐藏创建对象的好处是显而易见的,这样可以不会让领域层的业务逻辑泄漏到应用层,同时减轻了应用层的负担,只需要简单的调用领域工厂创建出期望的对象即可。
仓储
设计原因:领域模型中的对象从被创建出来后不会一直留在内存中,当他不活动时会被持久化到数据库中,然后需要的时候会重建该对象;
重建对象就是根据数据库中已存储的对象的状态重新创建对象的过程;所以,重建对象是一个和数据库打交道的过程,从更广义的角度来理解,
我们经常会像集合一样从某个类似集合的地方根据某个条件获取一个或者一些对象,往集合中添加对象或移除对象,也就是说,我们需要提供一种机制,
可以提供类似集合的接口来帮助我们管理对象,仓储就是这样被设计出来的。
重建对象就是根据数据库中已存储的对象的状态重新创建对象的过程;所以,重建对象是一个和数据库打交道的过程,从更广义的角度来理解,
我们经常会像集合一样从某个类似集合的地方根据某个条件获取一个或者一些对象,往集合中添加对象或移除对象,也就是说,我们需要提供一种机制,
可以提供类似集合的接口来帮助我们管理对象,仓储就是这样被设计出来的。
仓储里面存放的对象一定是聚合,原因是之前提到的领域模型中是以聚合的概念区划分边界的,事实上我们把整个聚合看成一个整体概念,要么一起被
取出来,要么一起被删除,我们永远不会单独对某个聚合内的子对象进行单独查询或做更新操作,因此,我们只对聚合设计仓储。
取出来,要么一起被删除,我们永远不会单独对某个聚合内的子对象进行单独查询或做更新操作,因此,我们只对聚合设计仓储。
仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,在领域模型中我们定义仓储的接口,而在基础设施层实现具体的仓储。这样做的原因:
仓储背后的实现都是在和数据库打交道,我们又不希望客户(如应用层)把重点放在如何从数据库获取数据的问题上,因为这样会导致(应用层)代码混乱,
很可能会因此忽略了领域模型的存在,所以我们需要提供一个简单明了的接口,供客户使用,确保客户以最简单的方式获取领域对象,从而让他专心的不会被
什么数据访问代码打扰的情况下协调领域对象完成业务逻辑。这种痛殴接口来隔离封装变化的做法很常见,由于客户面对的是抽象的接口并不是具体的实现,所以我们可以随时替换仓储的真正实现。
仓储背后的实现都是在和数据库打交道,我们又不希望客户(如应用层)把重点放在如何从数据库获取数据的问题上,因为这样会导致(应用层)代码混乱,
很可能会因此忽略了领域模型的存在,所以我们需要提供一个简单明了的接口,供客户使用,确保客户以最简单的方式获取领域对象,从而让他专心的不会被
什么数据访问代码打扰的情况下协调领域对象完成业务逻辑。这种痛殴接口来隔离封装变化的做法很常见,由于客户面对的是抽象的接口并不是具体的实现,所以我们可以随时替换仓储的真正实现。
尽管仓储可以像集合一样在内存中管理对象,但是仓储并不负责事务处理,一般事务处理会交给一个“工作单元”
设计模型的一般步骤
初步定义领域模型:根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及关联,关联可以暂时没有方向但需要有
(1:1,1:N,M:N)这些关系,可以用文字精确的没有歧义的描述出每个领域概念的含义以及主要信息
(1:1,1:N,M:N)这些关系,可以用文字精确的没有歧义的描述出每个领域概念的含义以及主要信息
识别领域以及应用层职责:分析主要的软件应用程序功能,识别出主要的应用层的类;及早发现哪些
是应用层职责,哪些是领域层职责
是应用层职责,哪些是领域层职责
进一步识别出领域中的概念:进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务
深入分析关联:分析关联,通过对业务的更深入分析以及各种软件设计原则以及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联
找出聚合边界以及聚合根:这是一件很有难度的事情;因为在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题,
所以,需要平时一些分析经验的积累找出正确的聚合根
所以,需要平时一些分析经验的积累找出正确的聚合根
为聚合根配备仓储:一般情况下是为一个聚合配备一个仓储,设计好仓储的接口
走查场景,确定我们设计的领域模型能够有效地解决业务需求;
考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数
停下来重构模型。寻找模型中觉得有问题的地方,如:思考一些对象应该通过关联导航得到还是应该从仓储获取?聚合设计的是否正确?模型性能等
总结:领域模型是一个不断重构,持续完善模型的过程,大家会在讨论中将变化的部分反映到模型中,从而模型不断细化并朝正确的方向走,
领域模型是领域专家、设计人员、开发人员之间沟通交流的过程,是大家工作和思考问题的基础。
领域模型是领域专家、设计人员、开发人员之间沟通交流的过程,是大家工作和思考问题的基础。
分层架构中其他层如何与领域层交互
从经典的领域驱动设计分层架构中可以看出,领域层的上层是应用层,下层是基础设施层。
对于会影响领域层中领域对象状态的应用层功能
对于修改领域对象的情况,通过仓储获取领域对象,调用领域对象相关业务方法以完成业务逻辑处理
对于新增领域对象的情况,通过构造函数或工厂创建出领域对象,如果需要还可以继续对该新创建的
领域对象做一些操作,然后把该新创建的领域对象添加到仓储中
领域对象做一些操作,然后把该新创建的领域对象添加到仓储中
对于删除领域对象的情况,可以先把领域对象从仓储中取出来,然后将其从仓储中删除,也可以直接
传递一个要删除的领域对象唯一标识给仓储通知其移除该唯一标识对应的领域对象。
传递一个要删除的领域对象唯一标识给仓储通知其移除该唯一标识对应的领域对象。
如果一个业务逻辑涉及到多个领域对象,则调用领域层中的相关领域服务完成操作
注意:以上所说的所有领域对象都是指聚合根,另外在应用层需要获取仓储接口以及领域服务接口时,
都可以通过IOC容器获取,最后通知工作单元提交事务从而将所有相关的领域对象的状态以事务的方式持久化
到数据库中。
都可以通过IOC容器获取,最后通知工作单元提交事务从而将所有相关的领域对象的状态以事务的方式持久化
到数据库中。
关于工作单元的几种实现方法
基于快照实现:领域对象被取出来后,会先保存一个备份的对象,然后当再做持久化操作时,将最新的对象状态和备份的对象状态进行比较,
如果不相同,则认为做过修改,然后进行持久化;这种设计的好处是对象不用告诉工作单元自己的状态修改了,而缺点是性能比较低,备份对象以及
对象状态是否有修改的过程在当对象本身很复杂的时候,往往是一个比较耗时的操作,而且要真正实现对象的深拷贝以及判断属性是否修改还是比较困难的。
如果不相同,则认为做过修改,然后进行持久化;这种设计的好处是对象不用告诉工作单元自己的状态修改了,而缺点是性能比较低,备份对象以及
对象状态是否有修改的过程在当对象本身很复杂的时候,往往是一个比较耗时的操作,而且要真正实现对象的深拷贝以及判断属性是否修改还是比较困难的。
不基于快照:仓储的相关更新或新增或删除接口被调用时,仓储通知工作单元某个对象被新增了或更新了或删除了。这样工作单元再做数据持久化时也同样知道需要持久化哪些对象,这种方法理论上不需要ORM框架支持,对领域模型也没有侵入性,同时也很好的支持工作单元的模式。
不基于快照,也不用仓储告诉工作单元数据更改了:而是采用AOP的思想,采用透明代理的方式进行一个拦截,在Hibernate中,属性通常要被声明为virtual的,一个原因就是NHibernate会生成一个透明代理,用于拦截对象的属性被修改时,自动通知工作单元对象的状态被更新了。这样工作单元也同样知道需要持久化哪些对象了。这种方法对领域模型的倾入性不大,并且能很好的支持工作单元模式,如果用NHibernate作为ORM,这种方法用的比较多;
对于不会影响领域中领域对象状态的查询功能
可以直接调用仓储查询出所需要的数据,但一般领域层中的仓储提供的查询功能也许不能满足界面显示的需要,
则可能需要调用多次不同的仓储才能获取所需要的数据;其实针对这种查询的情况,后面可以直接通过CQRS架构来实现,
即对于查询,可以在应用层不调用领域层任何东西,而是直接通过某个其他的用另外技术架构实现的查询引擎来完成查询,比如直接
通过构造参数化SQL的方式从数据库一个或多个表中查询出任何想要显示的数据,这样不仅性能高,也可以减轻领域层负担,领域模型
不太适合为应用层提供各种查询服务,因为往往界面上要显示的数据是很多对象的组合,是一种非对象概念的信息。
则可能需要调用多次不同的仓储才能获取所需要的数据;其实针对这种查询的情况,后面可以直接通过CQRS架构来实现,
即对于查询,可以在应用层不调用领域层任何东西,而是直接通过某个其他的用另外技术架构实现的查询引擎来完成查询,比如直接
通过构造参数化SQL的方式从数据库一个或多个表中查询出任何想要显示的数据,这样不仅性能高,也可以减轻领域层负担,领域模型
不太适合为应用层提供各种查询服务,因为往往界面上要显示的数据是很多对象的组合,是一种非对象概念的信息。
一些相关架构
CQRS架构
(Command Query Responsibility Segregation)
(Command Query Responsibility Segregation)
概念:CQRS是一种架构体系模式,能够使改变模型的状态命令和模型状态的查询实现
分离。
分离。
背景问题:在以前的管理系统中,命令和查询通常使用的是数据访问层中Responsibility中的实体对象(这些
对象时对DB中表的映射),这些实体有可能是SQL Server中的一行数据或者多个表。
通常对DB执行增、删、改、查都是针对系统实体对象,如通过数据访问层获取数据,然后通过数据传输对象DTO
传给表现层,或者用户更新数据,通过DTO对象将数据传给Model,然后通过数据访问层写回数据库,系统中所有交互
都是和查询和存储有关,可以认为是数据驱动的。
传统的CRUD存在一些问题:
1. 使用同一个对象实体来进行数据库读写可能会太粗糙,大多数情况下,编辑的时候只需要更新个别字短,但是却需要将整个
对象传进去,有些字段不需要更新,查询的时候在表现层可能只需要个别字短,但是需要查询和返回整个实体对象。
2. 使用同一实体对象对同一数据进行读写操作的是欧,可能会遇到资源竞争,经常要处理锁的问题,写入数据的时候,需要加锁,
读取数据的时候需要判断是否允许藏独。
3. 同步的,直接与数据库进行交互在大数据量同时访问的情况下可能会影响性能和响应性,并且可能产生性能瓶颈
4. 由于对于同一实体会在读写操作中用到,所以对于安全和权限的管理比较复杂。
解决方法:经常用到对数据库进行读写分离,让猪数据库处理事务性的增、删、改,从数据库处理查询。数据库复制被用来将事务性操作导致的
变更同步到集群中的从数据库。这是从DB角度处理了读写分离,但是从业务上将读写分离,就需要用到接下来的命令查询职责分离模式。
对象时对DB中表的映射),这些实体有可能是SQL Server中的一行数据或者多个表。
通常对DB执行增、删、改、查都是针对系统实体对象,如通过数据访问层获取数据,然后通过数据传输对象DTO
传给表现层,或者用户更新数据,通过DTO对象将数据传给Model,然后通过数据访问层写回数据库,系统中所有交互
都是和查询和存储有关,可以认为是数据驱动的。
传统的CRUD存在一些问题:
1. 使用同一个对象实体来进行数据库读写可能会太粗糙,大多数情况下,编辑的时候只需要更新个别字短,但是却需要将整个
对象传进去,有些字段不需要更新,查询的时候在表现层可能只需要个别字短,但是需要查询和返回整个实体对象。
2. 使用同一实体对象对同一数据进行读写操作的是欧,可能会遇到资源竞争,经常要处理锁的问题,写入数据的时候,需要加锁,
读取数据的时候需要判断是否允许藏独。
3. 同步的,直接与数据库进行交互在大数据量同时访问的情况下可能会影响性能和响应性,并且可能产生性能瓶颈
4. 由于对于同一实体会在读写操作中用到,所以对于安全和权限的管理比较复杂。
解决方法:经常用到对数据库进行读写分离,让猪数据库处理事务性的增、删、改,从数据库处理查询。数据库复制被用来将事务性操作导致的
变更同步到集群中的从数据库。这是从DB角度处理了读写分离,但是从业务上将读写分离,就需要用到接下来的命令查询职责分离模式。
什么是CQRS:基本思想在于,任何一个对象的方法可以分为两大类:
1. 命令:不返回任何结果,但会改变对象状态
2. 查询:返回结果,但是不会改变对象状态,对系统没有副作用。
操作和查询分离使得我们能够更好的把握对象的细节,更好的理解哪些操作会改变,当然CQS也有一些缺点,比如代码需要处理多线程情况。
CQRS使用分离的接口将数据查询操作和数据修改操作分离,意味着在查询和更新过程中使用的数据模型也是不一样的。
主数据库处理CUD,从数据库处理R,从库的结构可以和主库的结构完全一样,也可以不一样,从库主要用来进行只读的查询操作。数量上,从库
的个数也可以根据查询的规模进行扩展,业务逻辑上,也可以根据专题从主库中划分不同的从库,从库也可以实现成ReportingDataBase,根据查询的
业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。
1. 命令:不返回任何结果,但会改变对象状态
2. 查询:返回结果,但是不会改变对象状态,对系统没有副作用。
操作和查询分离使得我们能够更好的把握对象的细节,更好的理解哪些操作会改变,当然CQS也有一些缺点,比如代码需要处理多线程情况。
CQRS使用分离的接口将数据查询操作和数据修改操作分离,意味着在查询和更新过程中使用的数据模型也是不一样的。
主数据库处理CUD,从数据库处理R,从库的结构可以和主库的结构完全一样,也可以不一样,从库主要用来进行只读的查询操作。数量上,从库
的个数也可以根据查询的规模进行扩展,业务逻辑上,也可以根据专题从主库中划分不同的从库,从库也可以实现成ReportingDataBase,根据查询的
业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。
CQRS与EventSourcing 的关系: 在CQRS中,查询方面,直接通过方法查询数据库,然后通过DTO将数据返回。操作方面,通过发送Command实现,
由CommandBus处理特定的Command,然后由Command将特定的Event发布到EventBus,然后EventBus使用特定的Handler来处理事件,执行一些诸如
修改,删除,更新等操作。这里,所有与Command相关的操作都通过Event来实现,这样我们可以通过记录Event来记录系统运行的历史记录,并且方便的
回滚到某一历史状态。EventSourcing就是用来进行存储和管理事件的。
由CommandBus处理特定的Command,然后由Command将特定的Event发布到EventBus,然后EventBus使用特定的Handler来处理事件,执行一些诸如
修改,删除,更新等操作。这里,所有与Command相关的操作都通过Event来实现,这样我们可以通过记录Event来记录系统运行的历史记录,并且方便的
回滚到某一历史状态。EventSourcing就是用来进行存储和管理事件的。
CQRS架构实现
CQRS在读写方面分离,在读方面,通过QueryFacade到数据库里读取数据,在写方面,操作通过Command
发送到CommandBus,然后特定的CommandHandler处理请求,产生对应的Event,将Event持久化后,通过EventBus
特定的EventHandler对数据库进行修改操作。
发送到CommandBus,然后特定的CommandHandler处理请求,产生对应的Event,将Event持久化后,通过EventBus
特定的EventHandler对数据库进行修改操作。
架构图
Command Bus(命令总线):放在command Handler之前,可以看作是Comamnd发布者。
Command Handler(命令处理器): 处理来自commandBus分发的请求,可以看作是Command 订阅者,处理者
Event Bus(事件总线): 在command Handler完成之后,可以看作是Event发布者
Event Handler(事件处理器):处理来自Event Bus分发的请求,可以看作是Event订阅者,处理者
Event Store(事件存储): 对应概念Event Sourcing(事件溯源),可以用于事件回放处理,还原指定对象状态.
上面有些是EDA(事件驱动架构)中的概念,首先抽离两个重要概念:Command(命令)和Event(事件),Command是一种命令的语气,效果就是对某种对象状态的修改,Command Bus收集来自UI的Command命令,并根据具体命令分发给具体的Command Handler进行处理,这时就会产生一些领域操作,并对相应的领域对象进行修改,Command handler只是修改操作,并不会涉及到修改之后的操作(如保存、事件发布),Command Handler完成之后并不表示这个command命令就此结束,他需要把接下来的操作交给Event Bus来处理,并分发给相应的Event Handler订阅者进行处理,一般是数据保存、事件存储等。关于 Event Handler保存领域状态操作,他的实现一般会采用异步的方式,也就是说领域状态的保存操作不会延时领域中的业务操作。数据的一致性使用Unit Of Work,具体的领域状态保存用Repository实现,梳理Command整个流程,你会发现一个关键词,状态(status),Command Bus接受来自UI的请求,分发给相应的Command Handler进行处理,在处理过程中,就会对领域对象进行修改操作,但他不会保存修改之后的状态信息,而是交给Event Handler进行保存状态信息。
和Command相比,Query处理流程就很简单,Query Service接受来自UI的查询请求,这个查询处理可以使用各种方式实现,例如ORM,或者SQL,怎么能提高性能怎么来!返回的结果类型一般是DTO,根据UI进行设计,可以减少不必要的数据传输。
和Command相比,Query处理流程就很简单,Query Service接受来自UI的查询请求,这个查询处理可以使用各种方式实现,例如ORM,或者SQL,怎么能提高性能怎么来!返回的结果类型一般是DTO,根据UI进行设计,可以减少不必要的数据传输。
CQRS通过在业务上分离操作和查询使得系统具有良好的可扩展性和性能,使得能对系统不同部分进行扩展和优化,在CQRS中,所有涉及到对DB的操作
都是通过发送Command,然后特定的Command触发对应事件来完成操作,这个过程是异步的,并且所有涉及到对系统的变更行为都包含在具体的事件中。结合Eventing Source模式,可以记录下所有的事件。这些信息可以作为系统的操作日志,对系统进行回退或者重放。
都是通过发送Command,然后特定的Command触发对应事件来完成操作,这个过程是异步的,并且所有涉及到对系统的变更行为都包含在具体的事件中。结合Eventing Source模式,可以记录下所有的事件。这些信息可以作为系统的操作日志,对系统进行回退或者重放。
Event Sourcing(事件溯源)
对于DDD中的聚合,不保存聚合的当前状态,而是保存对象上发生的每个事件,当要重建一个
聚合对象时,可以通过回溯这些事件(即让这些事件重新发生)来让对象恢复到某个特定的状态;
因为有时一个聚合可能会发生很多事件,所以如果每次要在重建对象时都从头回溯事件,会导致性能地下,
所以我们会在一定时候为聚合创建一个快照,这样就可以基于某个快照创建聚合对象。
聚合对象时,可以通过回溯这些事件(即让这些事件重新发生)来让对象恢复到某个特定的状态;
因为有时一个聚合可能会发生很多事件,所以如果每次要在重建对象时都从头回溯事件,会导致性能地下,
所以我们会在一定时候为聚合创建一个快照,这样就可以基于某个快照创建聚合对象。
收藏
收藏
0 条评论
下一页
为你推荐
查看更多