DDD领域驱动设计
小伙伴们,你们好,我是老寇,前段时间,老寇刚看完张健飞老师的两本书《代码精进之路:从码农到工匠》和《程序员的底层思维》,书中的内容让我受益匪浅,因此,我把对COLA的理解做成专栏分享给大家,跟我一起学习COLA吧!
在学习COLA之前,我们先要了解架构的演进过程
六边形/洋葱/整洁/清晰架构
发展历程
区别对比
架构 | 介绍 | 不能解决问题 |
---|---|---|
六边形架构 | 六边形架构(也叫"端口和适配器"架构)主要关注系统的可扩展性和与外部世界的交互。通过"端口"和"适配器"的概念,清晰地将应用的核心业务逻辑与外部接口依赖(Web服务、数据库)分离,使得应用能够更加灵活地与外部世界交互(易于测试与扩展)。 | 1.应对复杂得业务建模问题没有提供具体解决方案,不能帮助处理复杂业务逻辑和领域建模2.在实施过程中,团队可能面临沟通和协作上挑战,尤其是在大型项目中,不同团队间如何管理端口和适配器的职责分配。 |
洋葱架构 | 洋葱架构的设计灵感来自于六边形架构,但它更强调于层次化的组织结构。它通过将应用分为多个层次,从核心业务逻辑到外部依赖,形成一个"洋葱"的结构。每一层都有明确的责任,核心业务逻辑位于最内层,外部技术和实现细节在最外层(Web服务、数据库)。当需要更换外部技术栈时,只修改外层,不会影响到核心业务逻辑。 | 1.洋葱模型本身没有提供有效的领域建模方法,对于复杂业务领域的建模问题仍然需要其他架构模型(领域驱动DDD)来补充。2.如果需要非常多种类的外部接口或技术栈交互,适配层变得非常庞大和复杂,难以管理。 |
整洁架构 | 整洁架构是对六边形架构和洋葱架构的进一步提炼和延伸。强调高内聚,低耦合设计,通过将不同功能的代码划分到不同的分层(核心业务逻辑,用例层,接口层等),每个层之间耦合最小,从而实现高内聚和低耦合。通过环形分层来组织代码,核心业务逻辑位于最内层,外部技术(RPC,数据库)与业务逻辑解耦,可以灵活替换。与六边形架构类似,整洁架构通过依赖规则确保内层不依赖外层,依赖注入帮助实现层与层之间的解耦。 | 1.设计和实现比较复杂,为系统带来不必要的复杂度,尤其是对于中小型项目,过于严格的分层可能带来不必要的复杂性。2.整洁架构的规则和约定对新手不太友好,尤其是没有使用依赖注入的开发者来说,掌握整洁架构的设计理念和实践可能需要一定时间。 |
清晰架构 | 清晰架构对整洁架构进一步优化,它强调可理解性和简洁性,核心思想是以业务需求为核心,设计清晰的模块划分,使得系统的结构和职责(CQRS职责分离)尽量简单明了。通过简洁架构,减少不必要的分层,提高开发效率,并降低因复杂设计带来的开发障碍。清晰架构侧重于模块之间的清晰分界和简洁性,避免不必要的复杂结构。 | 功能和复杂度高时难以扩展。 |
术语
学习DDD术语前,我们先来看一张COLA分层架构图,方便加深对DDD术语的理解
战术设计/战略设计
众所周知,DDD领域驱动设计分为 战略设计 (业务角度考虑领域划分)和 战术设计(技术角度考虑详细的设计和编码)
分层架构
DDD分层架构是一种旨在实现"高内聚低耦合"的架构设计,包括用户接口层 、应用层 、领域层 和基础层。该架构模式通过明确的职责和依赖关系,有效降低各层之间的依赖关系,实现清晰的架构边界,促进架构演进和业务逻辑的清晰表达。在微服务架构的演进过程中,聚合被视为基础单元,可以独立重组或拆分,促进领域模型和微服务架构的演进。
聚合根/聚合/实体/值对象
了解聚合根/聚合/实体/值对象,之前,先浏览一下代码加深印象
java
/**
* 用户实体[有唯一标识].
*/
@Getter
public class UserE extends Identifier {
/**
* 用户名.
*/
private String username;
}
/**
* 验证码值对象[无唯一标识].
*/
public record CaptchaV(String uuid, String captcha) {
}
/**
* 认证聚合[有唯一标识].
*/
@Getter
public class AuthA extends AggregateRoot {
/**
* 用户名.
*/
private final String username;
/**
* 密码.
*/
private final String password;
/**
* 租户编号.
*/
private final String tenantCode;
/**
* 认证类型 mail邮箱 mobile手机号 password密码 authorization_code授权码.
*/
private final GrantType grantType;
/**
* 验证码值对象.
*/
private final CaptchaV captcha;
/**
* 用户实体.
*/
private UserE user;
}
/**
* 聚合根[有唯一标识].
*/
@Getter
public abstract class AggregateRoot extends Identifier {
/**
* 服务ID.
*/
protected String serviceId;
/**
* 租户ID.
*/
protected Long tenantId;
/**
* 用户ID.
*/
protected Long userId;
/**
* 操作时间.
*/
protected final Instant instant = DateUtil.nowInstant();
}
/**
* 唯一标识.
*/
@Getter
public abstract class Identifier implements Serializable {
/**
* ID.
*/
protected final Long id = IdGenerator.defaultSnowflakeId();
}
术语 | 定义 | 特征 | 示例 |
---|---|---|---|
聚合根 | 聚合中的根实体,负责整个聚合的一致性和生命周期管理。 | 1.控制聚合内所有实体和值对象。2.通过聚合根进行外部访问。 | 认证AuthA |
聚合 | 一组相关的实体和值对象,聚合根是聚合的一部分,保证聚合的一致性。 | 1.由一个聚合根管理。 2.聚合内的实体和值对象具有一致性。 | 认证AuthA聚合 |
实体 | 具有唯一标识的对象,生命周期由聚合根控制。 | 1.有唯一标识(ID)。2.可以跨时间改变状态。 | 用户UserE |
值对象 | 没有唯一标识的对象,主要用于描述某种属性,通常是不可变的。 | 1.无唯一标识。2.值相同即被认为是相等的。3.一般不可变。 | 验证码CaptchaV |
领域工厂
领域工厂(Domain Factory)是领域驱动设计(DDD)中的一个模式,它用于创建聚合根或实体等领域对象。工厂的目的是封装对象的创建过程,以确保对象的一致性、完整性和正确性。特别是在创建复杂对象或聚合时,工厂可以帮助管理这些对象的构建过程,确保它们处于有效状态。
java
public class DomainFactory {
public static AuthA getAuthA() {
return new AuthA();
}
public static UserE getUserE() {
return new UserE();
}
}
领域服务
领域服务 (Domain Service)是领域驱动设计(DDD)中的一个重要概念,它代表了系统中那些 不属于任何单个实体或聚合的业务逻辑。这些业务逻辑通常涉及多个实体或聚合之间的交互,或者是某些跨多个领域对象的操作。
java
public class DomainService {
private final UserGateway userGateway;
private final CaptchaGateway captchaGateway;
public DomainService(UserGateway userGateway, CaptchaGateway captchaGateway) {
this.userGateway = userGateway;
this.captchaGateway = captchaGateway;
}
public void auth(AuthA a) {
// ...
// ...
// ...
}
}
职责分离
学习CQRS之前,先浏览一下代码加深印象
java
/**
* 保存用户命令
*/
public class UserSaveCmd {
// ...
}
public class UserSaveCmdExe {
public void executeVoid(UserSaveCmd cmd) {
// ...
}
}
java
/**
* 修改用户命令
*/
public class UserModifyCmd {
// ...
}
public class UserModifyCmdExe {
public void executeVoid(UserModifyCmd cmd) {
// ...
}
}
java
/**
* 删除用户命令
*/
public class UserRemoveCmd {
// ...
}
public class UserRemoveCmdExe {
public void executeVoid(UserRemoveCmd cmd) {
// ...
}
}
java
/**
* 查看用户请求
*/
public class UserGetQry {
// ...
}
public class UserGetQryExe {
public Result<UserCO> execute(UserGetQry qry) {
// ...
}
}
java
/**
* 查询用户请求
*/
public class UserPageQry {
// ...
}
public class UserPageQryExe {
public Result<Page<UserCO>> execute(UserPageQry qry) {
// ...
}
}
职责 | 命令(Command) | 查询(Query) |
---|---|---|
主要目的 | 修改系统状态(例如,更新、删除、创建等)。 | 读取数据或查询系统状态。 |
操作类型 | 写操作(包括所有业务逻辑相关的操作)。 | 读操作(包括数据获取、汇总、搜索等)。 |
处理的模型 | 通常使用命令模型(如DTO、聚合根等)进行处理,包含业务逻辑和状态改变。 | 通常使用查询模型(如视图、投影等)来优化数据读取,通常不包含复杂的业务逻辑。 |
数据一致性 | 强一致性,确保数据的完整性和准确性。 | 最终一致性,通常为了高性能和响应速度,查询操作可能是异步的,并且可以容忍一定程度的延迟。 |
复杂度 | 处理业务逻辑、事务和验证等复杂操作。 | 关注高效数据检索、优化查询和分布式数据源的处理。 |
优化目标 | 优化写入性能、事务管理、数据一致性等。 | 优化读取性能、减少数据库查询开销、提高响应速度等。 |
通常使用的模式 | 聚合根(Aggregate Root)、领域服务(Domain Service)等。 | 视图模型(View Model)、投影(Projection)等。 |
系统设计 | 设计复杂的领域模型,支持业务操作和状态变更。 | 设计高效的读取模型,通常是数据库表的投影或专门优化的查询接口。 |
实现方式 | 采用命令处理器(Command Handler)来处理。 | 采用查询处理器(Query Handler)来处理。 |
其他术语
术语 | 英文 | 解释 |
---|---|---|
应用服务 | Application Service | 负责协调领域模型中的操作,处理用户的请求,但不包含复杂的业务逻辑。通常用于与外部系统的交互,提供业务服务的接口。 |
领域事件 | Domain Event | 领域内发生的某个重要事件,通常代表系统的状态变更。领域事件应该具有业务含义,并且会对其他系统或子系统产生影响。 |
事件溯源 | Event Sourcing | 通过存储领域事件来代替直接存储当前状态的做法。每个领域事件都代表了系统状态的变更,重放这些事件可以恢复系统的状态。 |
仓储 | Repository | 用于封装数据存取逻辑的设计模式。仓储为领域对象提供一种存取方法,使得领域模型不需要了解数据存储的具体实现。 |
防腐层 | Anti-Corruption Layer | 通过创建一个隔离层来防止外部系统或子系统的模型污染当前系统的核心领域模型。防腐层将外部系统的概念和接口映射到本系统的合适形式,从而避免了外部系统影响内部设计。 |
核心域 | Core Domain | 系统中的核心业务领域,是最重要的部分。它包含系统的主要业务逻辑,往往是最需要精心设计和优化的部分。核心域在整个系统架构中占据核心地位。 |
支撑域 | Supporting Domain | 负责支持核心域的子系统或模块,通常处理一些辅助性的任务,例如身份验证、用户管理等。支撑域对于核心域的运作至关重要,但不包含核心业务逻辑。 |
通用域 | Generic Domain | 通用的业务功能或服务,不依赖于特定的领域,通常是跨领域共享的服务,如身份认证、日志记录等。它通常是一个横向的模块,可以被多个不同的领域复用。 |
充血模型 | Rich Domain Model | 将领域对象设计为包含复杂业务逻辑的实体,而不是简单的数据结构。领域模型不仅持有数据,还承担行为和业务规则。 |
贫血模型 | Anemic Domain Model | 领域对象仅包含数据,不包含业务逻辑。业务逻辑通常被放在服务层中。这种模型比较简单,但会导致服务层变得非常臃肿,违反了面向对象设计的封装原则。 |
通用语言 | Ubiquitous Language | 领域专家和开发人员之间共享的语言,它确保所有人都使用相同的术语和概念来描述业务领域。通用语言的使用能避免沟通障碍,帮助团队成员更好地理解需求和设计。 |
我是老寇,我们下次再见