一、引言
2003年埃里克.埃文斯(Evic Evans)出版了《领域驱动设计》这本书后,DDD(Domain-Driven Design) ****从而诞生。
DDD核心思想是从业务视角出发,根据限界上下文边界划分业务的领域边界,定义领域模型,确定业务边界。在为服务落地时,建立业务领域模型与微服务代码模型的映射关系,从而保证业务架构与微服务系统架构的一致性。但DDD提出后在软件开发领域一直都是“雷声大雨点小”,直到MartjnFowler提出微服务架构后,DDD才真正迎来了自己的时代。
DDD首先从业务领域入手,划分业务领域边界,采用事件风暴工作坊方法,分析并提取业务场景中的实体、值对象、聚合根、聚合、领域事件等领域对象,根据限界上下文边界构建领域模型,将领域模型作为微服务设计的输入,进而完成微服务详细设计。用DDD方法设计出来的微服务,业务和应用边界非常清晰,符合“高内聚,低耦合”的设计原则,可以轻松适应业务模型变化和微服务架构演变。
二、DDD是什么?
领域驱动设计( DDD ) ,是一种模型驱动设计的方法论,通过领域模型捕获领域知识,使用领域模型构造更易维护的软件。
DDD是从系统的分析到软件建模的一套方法论。将业务概念和业务规则转换成软件系统中的概念和规则,从而降低或隐藏业务复杂性,是系统具有更好的扩展性,以应对复杂多变的现实业务问题。
它是一套完整且系统的设计方法,一种架构设计原则和思维。
DDD的核心思想:是通过领域设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
战略设计:主要从业务视角出发,建立 业务领域模型 ,划分领域边界,建立 通用语言 的 限界上下文 , 限界上下文 可以作为微服务设计的参考边界。
战术设计:从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括: 聚合根、实体、值对象、领域服务、应用服务和资源库 等代码逻辑的设计和实现。
三、战略设计
战略设计指的是整个领域进行分析和规划,确定领域中的概念、业务规划和领域边界等基础性问题。
- 强调业务战略上的重要,指导设计决策,以便减少各个部分之间的依赖,使得设计意图更为清晰同时又不是关键的操作性和系统性。
- 需把模型的重点放在捕获系统概念核心上。
3.1 领域
领域(Domain) 其实就是一个组织所要做的整个事情,以及这个事情下所包含的一切内容。这是一个范围概念,而且是面向业务的(不是面向技术,更不面向数据库),每个组织都有自己的人员、规则和流程,当你为该组织开发软件的时候,你要对的就是这个组织的领域。
例如:财税领域、宠物医疗领域都属于一个行业。
领域又可以分为多个子域,子域又包含核心域、通用域和支撑域。
核心域: 核心业务,决定产品和公司核心竞争力的子域。 (例如:商品、服务)
通用域: 同时被多个子域使用的通用功能子域。 (例如:权限、登陆)
支撑域: 支撑其他子域,非核心域和通用域。 (例如:评论、短信、文件)
如何理解领域?
比如我们买一套房子,肯定分成客厅、卧室、厨房、卫生间、书房等各个功能不同的区域来,这就是房屋领域中的子领域。
如何理解模型?
我们买完房子,就需要装修了,这时候请设计公司出设计图纸,就是一种模型设计。这个过程是就是建模。
模型是对领域和抽象和模拟。
3.2 限界上下文
领域边界就是通过限界上下文( Bounded Context ) 来定义的。
用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等通用语言有一个确切的含义,没有二义性。
理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题领域到软件的解决方案。
如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
通用语言
用于统一领域专家、产品、研发、测试使用的语言,避免需求理解不一致,设计与需求不一致,沟通不顺畅问题,简单来说大家在一起聊某个东西的时候能够明白彼此所说的是什么,场景不同,同一个词会有着不同含义。
例如:商品在不同的阶段有不同的表达形式。商品在销售阶段商品,在销售阶段结束后,商品进入了运输阶段,这时商品就变成了货物。
可见,同样的一件商品,由于业务领域边界的不同,这些通用语言的术语就有了不同的含义。限界上下文就是用来定义这些通用语言的上下文边界的。这个边界既是业务领域的边界,也是微服务拆分的边界。
四、战术设计
战术设计依赖于领域模型和通用语言,通过技术模型将领域模型和通用语言中的概念映射到代码实现中。随着模型的进化,代码实现也会进行重构,以更好地体现模型概念。
主要包括:
- 代表领域中的概念:实体、值对象、领域服务、模块
- 用于管理对象生命周期:聚合、工厂、仓库
- 用于集成或跟踪:领域事件
4.1 实体
这个词被我们广泛使用,甚至过分使用,实体是一个重要的概念。
定义:实体是拥有唯一标识和状态,且具有生命周期的业务对象。实体通常代表着现实世界中的某个概念,实体于领域模型密切相关,它是领域模型中多个属性、操作或行为的载体。
实体的代码形态一般有四种形态:
-
失血模型:模型仅仅包含数据的定义和getter/setter方法,业务逻辑和应用逻辑都放到服务层中。这种类在java中叫POJO。
-
贫血模型:贫血模型中包含了一些业务逻辑,但不包含依赖持久层的业务逻辑。这部分依赖于持久层的业务逻辑将会放到服务层中。
-
充血模型:充血模型中包含了所有的业务逻辑,包括依赖于持久层的业务逻辑。
-
胀血模型:胀血模型就是把和业务逻辑不相关的其他应用逻辑(例如授权,事务等)都放在领域模型中。
结合团队以及兄弟团队的实践,建议实体采用贫血模型,实体和领域服务共同构建领域模型。这样可以使的实体具备业务知识,但又不至于太过臃肿。
实体特点:
有ID标识,通过ID判断相等性,ID在聚合内唯一。依附于聚合根,生命周期有聚合根管理。实体一般会持久化,但是与数据库持久化对象不一定一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
4.2 值对象
定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念体。在DDD中用来描述领域的特定方面,并且是一个没有标识符的对象,叫做值对象。值对象没有唯一标识,没有生命周期,不可修改,当值对象改变时只能替换。
值对象的业务形态:大多数情况下实体具有很多属性,这些属性一般都是平铺,但有的属性进行归类和聚合后能够表达一个业务含义,就将这些属性封装到一起形成值对象,从而方便沟通而无需关注细节,因此可以说值对象就是用来描述实体的特征。当然实体的单一属性也是值对象。
值对象的代码形态:值对象有两种:单一属性的值对象,例如字符串、整型、枚举型; 多个属性的值对象,这时候设计成class,包含多个属性,但没有ID,值对象中可以嵌套值对象。
值对象特点:
无ID,不可改变,无生命周期,用完就不再需要了。值对象之间通过属性值判断相等性。核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征,值对象尽量引用值对象。
4.3 聚合和聚合根
聚合(Aggregate):
聚合是一种封装,就是把具有相同生命周期、且在业务上不可分割的实体和值对象组合在一起。
聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
聚合属于DDD领域层,领域层包含多个聚合,共同实现核心业务逻辑。
跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。
领域、子域、限界上下文、聚合都是用来表示一个业务范围,那他们的关系是怎样的呢?领域、子域、限界上下文属于战略设计,而聚合属于战术设计,聚合的范围是小于前三者的。
聚合根:
聚合根是为了避免由于复杂数据模型缺少统一的业务规则控制,从而导致聚合、实体之间数据不一致的问题。
聚合可以比作组织,聚合根就是这个组织的负责人。
外部对象不能直接访问聚合内实体,需要先访问聚合根,再导航到聚合内部实体。
聚合根特点:
聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象引用的方式进行组织和协调,聚合根和聚合根之间通过ID关联的方式实现聚合之间的协同。
例如:在电商交易场景中,订单就是聚合根,订单项,收货地址、用户信息共同聚合成订单。
4.4 工厂
工厂(Factory)是一种重要的设计模式,DDD只是拿来主义,用到了工厂。
考虑使用工厂的主要动机:
将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域涉及的一部分。工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个时间被创建的对象。对于聚合来说,我们应该一次性的创建整个聚合,并且确保它的不变条件的满足。
4.5 资源库
资源库(Repository)是一种模式,用于封装数据访问逻辑,提供数据的持久化和查询。它旨在将数据访问细节与领域模型分离,使领域模型更加独立和可测试。资源库提供了一种统一的接口,使得领域模型可以与不同的数据存储方式(如关系数据库、文档数据库、内存数据库等)进行交互,同时也提供了一些查询操作,以便于在领域层进行数据查询。如果我们使用MyBatis的话,Mapper就是对资源库的一种实现。
4.6 领域服务
有些领域中的动作看上去并不属于任何对象。它们代表了领域中的一个重要的行为,不能忽略它们或者简单的把它们合并到某个实体或者值对象中。当这样的行为从领域中被识别出来时,推荐的实践方式是将它声明成一个服务,这个服务就是领域服务。
4.7 领域事件
领域事件用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解藕的同时,有助于形成完整的业务闭环。
领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,可以实现领域魔性的解藕,维护领域模型的独立性和数据的一致性。微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。
领域事件的执行需要一系列的组建和技术支撑:事件的构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理。
五、总结
本篇文章主要介绍领域驱动设计基本概念,并引出DDD核心思想,这和我们长久以来以数据表设计为核心的出发点完全不同。
下一章我们介绍领域驱动设计分层。
当然也可以找个在线学习课程 sanzhishu 点 top / 679 点 html