**领域驱动设计(Domain-Driven Design,DDD)**是一个有关软件开发设计的方法论,它提出了从业务设计到代码实现一致性的要求,不再对分析模型和实现模型进行区分。简言之,从代码结构我们就可以直接理解业务的设计,命名得当的话,非程序人员也可以“读”代码。
2003 年的时候,Eric Evans 发表了一篇著作**《Domain-driven Design: Tackling Complexity in the Heart of Software》**,正式定义了领域的概念,开始了 DDD 的时代,但 DDD 的发展确是一直不温不火,这个时候 spring 的贫血模型,则是风靡全球。
2013 年,Vaughn Vernon 写了一本**《Implementing Domain-Driven Design(实现领域驱动设计)》**进一步定义了 DDD 的领域方向,并且给出了很多落地指导,它让人们离 DDD 又进了一步。
而后,随着业务复杂性的提高,人们发现单体应用带来的迭代难,重构难,维护难等问题,越来越难以解决,于是在分而治之的思想下,诞生了微服务,一改以往单体应用,而拆分为多个子应用,一下子让人眼前一亮,于是我们没日没夜地拆分服务,加之微服务提供的注册中心、熔断、限流等解决方案,我们用得不亦乐乎。
但在人们在踩过诸多拆分服务的坑(拆分过细导致服务爆炸、拆分不合理导致频繁重构等)后,人们开始思考,到底有没有一种方法论可以指导人们更加合理地拆分服务呢?众里寻他千百度,DDD 却在灯火阑珊处,有了 DDD 的指导,加之微服务,再应对复杂业务场景上,才算是有了一个相对完美的解决方案。
一句话总结:领域驱动设计(DDD)既是一种开发思想体系,也是一种软件开发的方法论,它旨在管理为复杂问题域编写的软件的创建和维护工作。DDD 是模式、原则和实践的集合,它可以被应用到软件设计,以管理复杂性。
实施DDD所面临的挑战
其中 controller + jsp/thymeleaf 等可以认为是用户展示层,相信大部分同学都是从 SSM 框架入门的,大一些的项目可能会是前后端分离,即后端仅剩 controller 用于控制和前端的页面及数据交互.
如上图所示, spring 所提倡的简单的 java beans (即 pojo) ,相比以前的 ejb 大大简化了开发难度。
变化
主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。它是从高层业务的角度,来审视我们的软件系统.
如:战略设计角度来看,一套基础的电商业务应该包含如下领域,支付域、交易域、商品域、库存域、履约域。不同领域之间通过限界上下文来划分边界。
主要关注的是技术层面的实施,从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现
DDD 的领域就是这个边界内要解决的业务问题域。既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务范围就越大,反之则相反。领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域(每种子域一般包括多个限界上下文)。
在 DDD 领域建模和系统建设过程中,有很多的参与者,包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识,不同的参与角色可能会有不同的理解,那大家交流起来就会有障碍,怎么办呢?因此,在 DDD 中就出现了“通用语言”和“限界上下文”这两个重要的概念。这两者相辅相成,通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。
通用语言
团队交流达成共识后,可以简单清晰的描述业务规则和业务含义的语言,就是通用语言。它主要解决各岗位的沟通障碍问题,促进不同岗位的和合作,确保业务需求的正确表达。通用语言贯穿于整个设计过程,基于通用语言可以开发出可读性更好的代码,能准确的把业务需求转化为代码。
限界上下文
限界上下文主要用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
上下文映射图就通过画图的方式展示N(N>=2)个上下文之间的映射关系。其中 U(UpStream)代表上游,D(DownStream)代表下游。上下文之间存在多种组织和集成模式,分为合作关系、共享内核、客户方-供应方开发、遵奉者、防腐层、开放主机服务、发布语言、另谋他路、大泥球
领域专家并不是一个职位,他表示的是任何一个精通业务的人,相比于软件设计者和开发者,他可能了解更多有关业务领域的背景和知识,他可能是项目经理可能是产品经理可能是业务经理甚至有可能是一个保安大叔。
架构设计的最高原则,即建立一个高内聚松耦合的软件系统架构
最内层的是领域模型,封装了业务规则,不依赖任何其他组件,向外都是接口层。领域模型就是业务逻辑的模型,它应该是完全纯粹的,无论你选择什么框架,什么数据
库,或者什么通信技术,按照整洁架构的思想,都不应该去污染领域模型。
除了最中心的领域模型,数据库等基础设施可看作是南向网关, controller(REST Services) 是北向网关.内层领域模型通过接口和外部交互,业务汇聚在领域模型中.输入–>领域模型—>输出.
基于六边形架构的通用性,可用它来支撑系统中的其他架构,如面向服务 SOA 架构,REST、事件驱动架构或者命令和查询职责分离 CQRS 架构。
该图中,Aggregate为聚合,包括多个实体和值对象,实体是采用充血模型建立的对象,包含数据和行为.(贫血模型和充血模型至今仍有争论,尚无结果,故可有选择的使用充血模型)
个人认为的的最佳实践:
图中领域层,断开聚合(Agregate)与资源库(Responsitoryes)的依赖,聚合中仅放置自给自足的方法,对外部完全无依赖,与资源库相关的交互放到领域层的领域服务(Services)中.
SOA 对于不同的人来说具有不同的意思,所以这里只给出一些设计 SOA 的原则
CQRS本质上是一种读写分离设计思想,它具有以下特点
1、如果一个方法修改了对象的状态,该方法就是一个命令(Command),他不应该返回数据。在Java中,这样的方法应该声明为void
2、如果一个方法返回了数据,这个方法便是一个查询(Query),它不应该通过直接或间接的手段修改对象的状态,在Java中,这样的方法应该以其返回的数据进行声明。
查询
查询操作并不会修改数据库中的内容,所以查询本身是一种幂等操作,以同一个查询条件在系统不改变的情况下反复执行会返回相同的结果,我们可以针对这种特性提供数据缓存来提高系统性能;同时因为不影响数据库,查询逻辑是不会产生数据一致性问题。查询往往会存在较高的使用频率。
命令
命令操作会直接修改数据库,并针对多个领域模型的情况下我们需要增加来保证操作的原子性。而对于一个命令操作,我们往往是不直接依赖命令的返回值的,所以通常可以异步执行命令操作。对于一般系统来说,往往命令操作的使用频次会较低。
针对查询与命令的需求特点才有了CQRS的设计,具体设计上有以下几种分类
CQ两端在上层代码进行分离个字单独维护,例如命令型的都用xxxManagerController、xxxManagerService来定义,而查询则直接用xxxController、xxxService定义。
在上述a的基础上,由于更加关注性能,可在数据库层面采用硬件分离的读写分离模型
挑战
建议
对于以下场景不建议引入CQRS:
关于事务的理解见下图:
代码结构: