一、什么是DDD
DDD指通过统一语言、业务抽象、领域划分和领域建模等一些列手段来控制软件复杂度的方法论,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。
二、领域驱动
开发过程不再以数据模型为起点,而是以领域模型为出发点,领域模型对应业务实体。
程序中主要表现为类、聚合根和值对象,更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。
领域驱动的研发过程为:需求分析 ----> 领域分析 ----> 领域建模 ----> 核心业务逻辑 ----> 技术细节(DB、Cache、Message.....),数据驱动的研发过程为:需求分析 ----> 数据建模(ER图) ----> 建库建表,写DAO层 ----> 编写业务逻辑
三、MVC(Model-View-Controller)与DDD架构
-
关注点不同:
- MVC架构关注于将应用程序的不同部分分为模型(Model/Dao)、视图(View)和控制器(Controller),以实现分层和关注点分离。它主要解决的问题是用户界面与应用逻辑之间的分离,以及实现交互和数据展示。
- DDD架构关注于对领域知识的建模和实现,将领域层(Domain Layer)作为核心,并通过领域模型来表达业务领域的概念和规则。它主要解决的问题是在复杂的业务领域中,如何设计和组织代码以实现业务需求。
-
层次结构不同:
- MVC架构分为三个主要层次:模型(Model)、视图(View)和控制器(Controller)。模型层负责处理数据和业务逻辑,视图层负责展示数据给用户,控制器层负责处理用户的输入和操作。
- DDD架构也分为不同的层次,包括领域层(Domain Layer)、应用层(Application Layer)、基础设施层(Infrastructure Layer)等。领域层是核心,负责定义和实现领域模型和业务逻辑,应用层负责协调和组织领域层的操作,基础设施层负责与外部资源的交互。DDD分层依靠事件驱动,通过事件建立模型,合理划分边界,建立领域对象,定义符合DDD分层架构思想的代码模型,以此保证业务模型与代码模型的一致性。
-
设计思想不同:
- MVC架构的设计思想是通过分离用户界面、应用逻辑和数据操作,实现可维护、可扩展的应用程序。它强调关注点分离和模块化设计。
- DDD架构的设计思想是通过领域模型来表达业务领域的概念和规则,强调将业务逻辑放在核心领域层中,以实现高内聚、松耦合的设计。
四、DDD基础
1. DDD层级划分
- 最内层:最基本的数据单位,值对象、属性集、唯一标识等,不能直接使用。
- 实体:它将基础数据进行封装,在代码中就是封装好的一个个实体对象,可直接使用。
- 领域层:它按照业务划分为不同的领域,比如订单领域、商品领域、支付领域等。
- 应用服务层(业务层):它对业务逻辑进行编排。
2. 通用语言
3. 限界上下文
4. 领域交互
领域之间的交互,也可以指不同上下文之间的业务实体要如何实现交互。
在DDD中这种机制叫做上下文映射(Context Mapping),一般方法有通过合作关系(一荣俱荣,一损俱损)、防腐层Anti-Corruption(通过一些适配和转换)、共享内核(依赖部分共享的模型)、依赖关系(有组织的上下游依赖)等。
其中需要特别关注防腐层,是隔离最彻底的做法,强调单独一层完成上下游的交互,与业务逻辑完全解耦,各自独立(是优点,也是缺点:存在一定的转换成本),例如在商品和采购子域提供防腐层,将商品的变更进行收口,隔离后端业务实现。
5. ORM(Object-Relational Mapping,对象-关系映射)
-
SQL映射:使用XML或注解的方式,将SQL语句映射到Java接口或类的方法上。通过定义映射关系,MyBatis可以自动执行SQL并将结果转换为Java对象。
-
参数处理:MyBatis支持在SQL语句中使用参数,并提供了各种处理参数的方式,如命名参数、位置参数、动态SQL等。
-
缓存机制:MyBatis提供了缓存机制,可以缓存查询结果,减少数据库访问次数,提高性能。它支持一级缓存和二级缓存,并提供了灵活的配置选项。
-
事务支持:MyBatis支持数据库事务的管理,可以通过注解或XML进行事务的声明和控制。
-
插件机制:MyBatis提供了插件机制,可以自定义和扩展框架的功能。开发人员可以通过插件来拦截SQL执行、修改SQL语句或结果等。
-
多数据源支持:MyBatis可以配置多个数据源,从而支持在一个应用程序中访问多个数据库。
6. 实体与值对象
实体(Entity)
拥有唯一标识的对象,它具有可变的状态和生命周期,实体 = 唯一身份标识 + 可变性【状态 + 行为】,实体通常被表示为DO(领域对象)的形式。例如,商品(Commodity)可以被认为是一个实体,每个商品都有唯一的商品ID作为标识,并且具有一些可变的属性,如商品名称、价格、库存等。商品可以参与到业务流程中,如下单、支付、发货等。
实体是根据标识,而不是其属性值来区分的。实体对象在系统中具有持久化的特性,通常会被存储到数据库中。
实体的行为可以通过方法来实现,方法可以直接操作和改变实体的状态和属性,这些行为使得实体不仅仅是简单的数据容器,而是具有一定的功能和行为,实体的行为包括以下几个方面:
-
属性访问和修改:实体通常具有一些属性,行为包括对这些属性进行读取和修改。例如,一个用户实体可能包含姓名、年龄等属性,行为可以是获取用户姓名或修改用户年龄的方法。
-
业务规则的执行:实体的行为可以涉及到执行特定的业务规则和约束。这些规则通常与实体的属性和状态有关,用于检查和保持实体的合法性。例如,一个订单实体可能包含一个方法用于检查订单是否符合某种特定的业务规则,如订单金额是否超过限额。
-
实体间的关联操作:实体可以与其他实体建立关联关系,并进行关联操作。例如,一个用户实体可能与多个订单实体关联,行为可以是获取用户的所有订单或添加一个新的订单。
-
业务流程的执行:实体通常具有业务逻辑和行为,可以参与到业务流程中,执行一系列的任务和操作。这些任务和操作可以涉及多个实体之间的协作和交互。例如,一个购物车实体可能包含将商品加入购物车、计算价格、生成订单等方法,用于执行完整的购物流程。
-
生命周期管理:实体的行为可以涉及到管理实体的生命周期,包括创建、使用和销毁等。例如,一个用户实体可能包含注册、登录、注销等方法,用于管理用户的生命周期。
-
值对象(Value Object)
也称为数值对象或数据传输对象,是用于封装一组相关属性的对象,它们没有唯一的标识,通常用于表示较简单的概念和属性组合,是不可变的,它们可以作为实体的属性或方法的参数,用于传递和封装属性值。基于其属性值来判断值对象是否相等。
例如,订单地址可以定义为一个值对象,订单地址通常由多个属性(如国家、省份、城市、街道等)组成一个完整的地址,如果两个订单地址的属性值完全相同,则认为它们是相等的。
实体与值对象之间的依赖,大多数情况下,应该是实体依赖值对象,值对象作为实体的描述属性存在。
7. 实体的充血模型
实体的充血模型强调将业务逻辑和行为封装在实体中,将实体打造成具有丰富行为和状态的对象,它不仅仅是简单的数据容器,还包含了业务逻辑和处理规则。实体负责管理自己的状态,拥有处理自身行为的方法,以及对外提供操作接口。
8. 聚合与聚合根
聚合
把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。聚合是领域对象的显式分组。
聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。例如:有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。
聚合根
也称为根实体,是一种更大范围的封装,把一组有相同生命周期,在业务上不可分隔的实体和值对象聚合在一起,通过根实体的唯一标识对外提供能力。它不仅是实体,还是聚合的管理者。
聚合之间通过聚合根ID关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。
9. 领域事件
它用来表示在一个特定领域由一个用户动作触发的,发生在过去的行为产生的事件。领域事件将会导致进一步的业务操作,有助于实现业务的解耦,并完成业务闭环。需要忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。
领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。
-
- 事件发布:构建一个事件,需要唯一标识,然后发布;
- 事件命名:事件是表示发生在过去的事情,在命名上推荐使用Domain Name + 动词的过去式 + Event,可以更准确地表达业务语义。如:MoneyTransferedEvent表示转账成功发出的事件、MoneyTransferFailedEvent表示转账失败发出的事件
-
- 事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;
- 事件分发:服务内直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等;
- 事件处理:先将事件存储,然后再处理。
例如:用户下订单成功后,发布领域事件,积分聚合与优惠券聚合监听订单发布的领域事件进行处理。
领域事件可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。通过领域事件+补偿机制来达到最终一致性,提高系统的稳定性和性能。
在一致性要求不高时,可以通过领域事件订阅器直接向消息队列发送事件。对一致性要求高时,需要先将事件进行持久化存储(如数据库),然后通过后台线程加载(可实现异步处理)并分发到消息队列。同时将事件存储后再分发到消息队列,可以确保事件按照一定的顺序进行处理,原因:
-
顺序的保证:存储系统通常会提供对数据的顺序写入操作,确保写入的顺序与事件的产生顺序一致。这样,当事件被后台线程加载并分发到消息队列时,它们会按照存储顺序被读取和处理。
-
避免并发冲突:将事件存储到数据库中期间,数据库通常会对并发写入操作进行合理的并发控制,例如使用事务或乐观锁等机制,以确保数据的一致性。这样可以避免不同线程或进程之间对同一事件的写入操作发生冲突,从而保证了事件的顺序性。
-
故障恢复:通过将事件存储到持久化存储中,即使系统发生故障或重启,仍然可以从存储中恢复事件。系统在重新启动后,可以按照存储的顺序重新加载事件,确保事件按照正确的顺序进行处理,从而保持一致性。
10. 资源库(仓储)
仓储介于领域模型和数据模型之间,主要用于聚合的持久化和检索。它隔离了领域模型和数据模型,以便我们关注于领域模型而不需要考虑如何进行持久化。
将暂时不使用的领域对象从内存中持久化存储到磁盘中。当日后需要再次使用这个领域对象时,根据 key 值到数据库查找到这条记录,然后将其恢复成领域对象,应用程序就可以继续使用它了,这就是领域对象持久化存储的设计思想。
五、DDD架构
1. 分层架构
严格分层架构:某层只能与直接位于的下层发生耦合。
松散分层架构:层间关系不那么严格,允许上层与任意下层发生耦合。每层都可能使用它下面所有层的服务,而不仅仅是下一层的服务。每层都可能是半透明的,这意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。
2. 分层
从上往下:
用户接口层 (interface)
业务应用层 (application)
领域层 (domain)
基础层 (infrastructrue)
用户接口层 (interface)
所有流量的请求入口,负责向用户显示信息和解释用户指令。用于处理客户端发送的请求和解析客户端相关特殊操作,并将数据传递给应用层进行处理。它只负责接收请求参数,并根据业务请求,将参数传递给应用层,并将应用层返回的结果再根据用户的需要进行组装,并最终返回给用户。
这里的用户可能是:用户(H5/APP)、程序(API)、消息队列(MQ)、超时中心回调,自动化测试和批处理脚本等等;这里的请求可能来自于web 请求、rpc 请求、mq 消息等外部输入的请求。
业务应用层 (application)
用于表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务流程的执行顺序以及服务结果的拼装。例如抽奖项目中领取活动、执行抽奖、结果落库、发送MQ消息触发发奖流程、返回结果,上述事件的完整执行顺序放在应用层中。
应用层连接用户接口层和领域服务层,它是很薄的一层,因为应用层位于领域层之上,主要职能是协调领域层多个聚合完成服务的组合和编排,协作完成业务操作。
应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。,同时一些权限判断也可以放在应用层。
应用层的服务分为两类:应用服务(由业务层自实现的操作,对外提供粗粒度服务);领域事件服务 (领域事件的发布与订阅、通过事件总线或消息队列实现异步数据传输实现服务解耦)
举例:假设出价分四步,那么应用层的主要功能就是编排这四步,不做具体的业务处理:
- 调用商品接口校验商品是否上架;(基础层处理,基础层封装外部RPC)
- 出价规则校验;(规则域执行)
- 数据落地;(出价域执行)
- 调用库存接口生成库存。基础层处理(基础层封装外部RPC)
领域层 (domain)
领域层封装了核心的业务逻辑,负责表达业务概念、业务状态信息以及业务规则,即包含了该领域所有复杂的业务知识抽象和规则定义。明确了需要解决的问题范围、及与其他业务域之间的关系和作业方式。
领域层的核心是对于领域对象的分析,包含聚合(聚合根)、实体、值对象、领域服务、领域事件、仓储、工厂等领域模型中的领域对象。
-
-
- 领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。
- 领域中跨实体或值对象的操作封装而成的服务。由多个业务职责单一的聚合构成,作用是实现领域核心业务逻辑,通过各种校验手段保证业务的正确性。
- 领域服务是对同一个实体的一个或多个方法进行组合和封装,或对多个不同实体的操作进行组合或编排,对外暴露成领域服务。
- 领域服务封装了核心的业务逻辑。实体自身的行为在实体类内部实现,向上封装成领域服务暴露。
-
领域层中定义的Repository接口是一种抽象,描述了对领域对象的增删改查操作,只是为了定义领域层对数据持久化的需求和操作方法,并不指定具体的实现方式,它只提供参数后要结果,具体的数据库操作和技术实现在基础设施层(Infrastructure Layer)中进行。
基础层(infrastructrue)
基础设施层是数据封装层,在这里获取各类数据,比如数据库,缓存,外部领域,外部接口等。
基础层是贯穿除领域层外所有层的,比较常见的功能是提供数据库持久化和外部领域服务调用:
-
-
- 为领域模型提供持久化机制,提供数据相关的操作实现,如数据库、缓存、ElasticSearch等,承接领域层所提供的参数,并实现领域层定义的Repository接口(如使用ORM框架的Repository实现),依赖数据库访问技术,执行相应的数据库操作,并将结果转换为领域对象返回给领域层。通过依赖反转的方式为各层提供基础数据资源服务。
- 对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。
- 为领域模型提供持久化机制,提供数据相关的操作实现,如数据库、缓存、ElasticSearch等,承接领域层所提供的参数,并实现领域层定义的Repository接口(如使用ORM框架的Repository实现),依赖数据库访问技术,执行相应的数据库操作,并将结果转换为领域对象返回给领域层。通过依赖反转的方式为各层提供基础数据资源服务。
-
基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
防腐层
在一个上下文中,有时需要对外部上下文进行访问, 通常会引入防腐层的概念来对外部上下文的访问进行一次转义。也被称适配层或者转换层。以下几种情况会考虑引入防腐层:
-
- 需要将上层的模型翻译成当前层可以理解的模型。比如上层定义了两个值id和type,不同的type所对应的id含义不同,那么翻译的时候,可以直接根据type进行转换。
- 不同上下文之间的团队协作关系,如果有依赖关系,建议引入防腐层,避免下层变动造成上层的变动,即避免参数的引用传递。
- 避免将上层过多的参数传递到下层。
common(公共包)
指包含通用的、与特定领域无关的功能和组件的代码库或模块,如枚举信息、时间工具类、各种工具类等。这些通用的功能和组件可以被多个领域模型和业务域共享和重用。通常包含以下类型的内容:
-
基础设施组件:这些组件提供与特定技术平台或框架相关的功能,例如数据库访问、日志、缓存、消息传递等。基础设施组件可以被不同的领域模型和业务域使用,以实现一致的技术实现和集成。
-
工具类和辅助函数:这些类和函数提供一些通用的功能和辅助方法,例如日期时间处理、字符串处理、文件操作等。它们是一些常见的工具和帮助类,可以被不同的领域模型和业务域使用,以提高开发效率和代码重用性。
-
共享模型和值对象:这些模型和值对象是与特定领域无关的概念和实体,可以被多个领域模型和业务域共享和重用。它们通常表示一些通用的数据结构、通用的业务概念或通用的领域规则。
-
通用配置和规范:这些配置和规范可以用于不同领域模型和业务域的一些通用设置和约定。例如,数据库连接配置、权限管理规范、日志记录规范等。这些配置和规范可以被多个领域模型和业务域共享和使用,以确保一致性和规范性。
-
3. 对象流转(各层间数据转换)
每一层属于自己的特定数据:
-
- interfaces层:request、response
- application层:DTO(data transfer object)
- domain层:entity、VO(value object)
- infrastructure层:PO(persist object)
首先用户接口层通过 request/response 对象来进行跨进程间的交互数据;应用服务层使用(DTO)来进行数据交互;在领域内部,我们通过领域对象(entity/VO)作为领域内部的数据和行为载体;在基础设施层,我们使用持久化对象(PO)进行数据库资源的交互。
4. POJO
plain ordinary java object 无规则的简单java对象,是DO/DTO/BO/VO(数据对象/数据传输对象/业务对象/值对象)的统称,它不依赖于特定的框架或技术。
POJO的设计思想是将Java对象简化为纯粹的数据封装和业务逻辑实现,以保持代码的简洁性和可读性。
- DO(Domain Object 领域对象)
是通常用于领域层或服务层中的对象,表示业务领域的概念和业务实体。DO中通常包含与业务相关的属性和方法,用于封装业务逻辑和处理业务操作。
- DTO(Data Transfer Object 数据传输对象)
表示用于在不同层或不同服务之间传输数据的对象。
DTO通常用于封装多个DO或其他业务对象,在网络传输或应用程序内部传递数据。用在需要跨进程或远程传输时,它不应该包含业务逻辑,主要用于远程调用等需要大量传输对象的地方。
例如:一张表有 100 个字段,那么对应的 PO 就有 100 个属性,但界面上只需要显示 10 个字段,客户端用 WEB service 来获取数据时,就没有必要把整个 PO 对象传递到客户端,这时就可以用只有这 10 个属性的 DTO 来传递结果到客户端,同时也不会暴露服务端的表结构,到达客户端以后,如果用这个对象来显示对应界面,那此时它的身份就转为 VO。
- VO(value object 值对象 / view object 表现层对象)
视图对象,主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
根据业务需要,可以和表对应,也可以不和表对应。可以细分包括 request、response。
- PO(persistent object 持久对象)
- 有时也被称为Data对象,通常是用于数据持久化层或数据访问层中的对象,它们与数据库表或数据实体直接对应。PO包含与数据库字段一一对应的属性和映射关系,以实现数据的持久化和访问,最形象的理解就是一个 PO 就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
- 在Mybatis持久化框架中与insert/delete操作密切相关,但不包含任何对数据库的操作。它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。
六、CQRS(Command Query Responsibility Segregation,命令查询责任分离)
CQRS是一种架构模式,在传统的架构中,通常使用相同的数据模型和接口来处理读写操作。而CQRS则将读写操作分离,可以针对每种操作类型使用不同的模型和接口,以满足它们各自的需求。
CQRS模式的核心思想是:
-
分离读写模型:将读操作和写操作分别使用不同的模型来处理。读模型可以针对查询性能进行优化,读模型的数据可以进行冗余、缓存和索引等优化操作。写模型可以更专注于处理命令操作,保证数据的一致性和完整性。
-
使用命令和事件:命令是用于触发写操作的指令,而事件是用于通知其他组件或服务关于数据更改的发生。命令和事件可以作为消息进行传递,实现解耦和异步处理。
-
独立的数据存储:读模型和写模型可以使用不同的数据存储技术和数据结构。读模型可以使用专门的数据存储(如缓存、索引数据库等)来提高查询性能,而写模型则可以使用传统的关系型数据库或NoSQL数据库。
-
CQRS通常与领域驱动设计(DDD)一起使用,两者可以相互增强和补充。
七、COLA(Clean Object-oriented, Low-coupling, High-cohesion, Agile)
COLA架构是一种面向业务的设计模式,它包括Context(上下文)、Object(对象)、Logic(逻辑)和Action(动作)四个部分,使得系统的业务流程和业务逻辑更加清晰,代码结构更加简洁,易于维护和扩展。
- Context(上下文):指的是业务场景上下文,用于描述当前业务场景的上下文信息,包括业务数据、流程状态、用户信息等。
- Object(对象):指的是业务对象,用于描述当前业务场景下的业务对象,比如订单、商品、用户等。
- Logic(逻辑):指的是业务逻辑,用于描述当前业务场景下的业务规则和逻辑,包括数据校验、状态转换、流程控制等。
- Action(动作):指的是业务动作,用于描述当前业务场景下可以执行的动作,比如提交、撤回、审核等。
COLA和DDD的区别:
- 设计思想:COLA架构的设计思想是基于CQRS(命令查询职责分离)和EDA(事件驱动架构)的,注重解耦、异步和可扩展性;而DDD的设计思想则注重领域建模和聚合设计,强调业务领域内的概念和规则。
- 架构风格:COLA架构是一种面向服务的架构风格,强调服务之间的解耦和自治;而DDD则更多地关注于领域内的数据和行为,主要采用对象和聚合的方式进行建模。
- 技术实现:COLA架构通常使用Spring Boot、MyBatis、RocketMQ等技术实现;而DDD则更多地采用面向对象的编程语言和框架,如Java和Spring。
八、MCOAP
- mos-mcoap-aci-facade:防腐层
- mos-mcoap-aci-infrastructure:防腐层
- mos-mcoap-application:应用层,包含应用整个功能核心的实现
- mos-mcoap-bootstrap:包含所有的应用启动的装配,只关心应用如何启动,不关心业务,相当于main方法
- mos-mcoap-common:统一放入工具类的对象,也会有一些常量、对metaq的封装、一些锁、日志、mybatis封装、工具包等
- mos-mcoap-domain:领域层,按照域划分的模块,包含所有的命令以及实现、领域对象
- mos-mcoap-facade:标准的对外接口层,只包含所有的hsf接口。
- mos-mcoap-gateway:网关层,主要为了解决外部的消息来源,会在这一层去完成消息的过滤以及处理
- mos-mcoap-nfrastructure:基础设施层,也就是我们所理解的传统意义上面的数据实现层。包含所有数据相关的操作,还有公用的中间件,比如说cqrs实现也是在这里面
- mos-mcoap-openfacade:定向开放平台