本文阅读指南:
为了更友好地阅读本篇文章,我强烈建议读者读一下阅读指南!
1、本文有许多地方结合代码去说明,我真的建议大家不要跳过代码,只读文字部分实在是太抽象了。
2、本文会将比较官方的解释和比较通俗的解释结合起来,尽可能的举例说明,因为官方话术听得是云里雾里,当然这是对于初学者而言,大佬请便!
3、其中涉及到的一些其他概念,本文不去专门解释的,希望读者先去弄懂那些概念再进行阅读,不过我会尽可能地解释或者给一些参考文章。
4、本文针对比较难理解的领域层和应用层进行了详细说明。用户接口层和基础服务层比较简单,就不做详细介绍了,文章中也有涉及,相信读者阅读过程中就会有体悟。
5、为了简化文章,我把一些不那么重要而读者又需要知道的东西放在了文章最后的补充里。
一、DDD架构分层
下面我们分别简单介绍一下这几层。
注:这里只是简单介绍,后面会有对应用层和领域层详细介绍。
1、用户接口层(也称为展示层、触发层)扮演着系统与外部世界交互的桥梁和“门面”角色。它的核心职责是处理用户的输入请求,并将系统的响应结果呈现给用户或其他系统。类似于MVC架构中的Controller层。
2、应用层(Application Layer) 是连接 用户接口层(UI/API 层) 和 领域层(Domain Layer) 的关键桥梁。它的核心职责是 协调业务用例的执行,但不包含具体的业务逻辑(业务逻辑应放在领域层)。
3、领域层。领域层一般包含模型、仓储(repository)接口、适配器(adapter)接口
、服务。这里的仓储、适配器只是定义一个标准,所以只是接口,具体实现在基础服务层。
4、基础设施层(Infrastructure Layer) 是支撑整个系统运行的技术底层,它负责 与外部世界交互 并提供 技术实现细节。它的核心职责是 让领域层和应用层不用关心技术细节,专注于业务逻辑。比如基础服务层会提供数据库访问,缓存,文件存储,身份认证等等服务。
对应的包结构:
二、领域层(domain)
为了便于理解,这里先介绍领域层,再介绍应用层。
包结构:
(1)领域:
这里先解释一下什么叫领域,说的直白一点,一个领域就是一个业务问题。
而图中model,service,repository,其实就是这个业务下关联的聚合(待会解释),业务逻辑,数据库操作。
(2)聚合:
聚合的本质就是把强相关的多个东西打包成一个整体来管理。形成一个充血模型。
充血模型:将状态、行为封装到一个类中
举个例子:比如电商系统中,一个“订单”(order)包含下面的属性:订单id,商品(item),支付方式等,这些属性就是这个聚合关联的实体或值对象。
另外,这里的addItem方法就是这个聚合的行为,这个行为是一种简单的业务逻辑(添加商品)。
// 示例:订单聚合根
public class Order {
private String orderId;
private List<OrderItem> items;
private Payment payment;
public void addItem(OrderItem item) {
items.add(item);
}
}
需要注意的是,外部不能直接使用聚合内部的属性,比如上述代码,如果我要增加商品。外部只能通过Order实例调用addItem方法来实现,而不能通过setItem方法去直接添加。因为后者会导致数据不一致问题。比如下面的代码。
public class Order {
private BigDecimal total;//商品总数
private List<OrderItem> items;//商品
public void addItem(Product product, int quantity) {
items.add(new OrderItem(product, quantity));
this.total = calculateTotal();
}
}
如果外部调用addItem方法,可以直接达到添加商品的目的。而如果通过setItem在items集合中添加商品,total记录的商品总数和items集合中数量就会不一致。一个聚合中的属性一般是具有紧密联系的,如果这个聚合比较复杂,通过外部直接访问聚合内的属性,就可能因为改动不一致而出现问题。
所以一个上述聚合只能通过Order实例来访问,这个访问该聚合的唯一入口,就叫该聚合的聚合根。
另外,在聚合中,DDD架构不建议将过于复杂的逻辑放在聚合中,尤其是在方法中还需要调用其他对象的方法的时候,这样就增加了聚合的交互,随着业务越来越复杂,耦合度也就越来越高,代码会变得难以维护。
那我们总不能永远不跨对象去做业务逻辑。为了解决这个问题,我们引入domain层中的service部分。
我们把一些复杂的业务(比如跨对象调用)逻辑存放在service包中,在service中调用、仓储、事件等等,但是service中的服务只能和当前业务(领域)有关。
这样,我们就实现了高内聚低耦合,从而能够让每个领域层独立演化。避免了MVC架构下,service之间互相调用产生的耦合问题。
另外,MVC架构中还有一个问题,就是随着业务越来越复杂,实体类的使用会变得混乱。比如,业务A觉得这个现有的实体类跟我需要的差不多,我就不自己创建了,我直接拿来用。业务B觉得,这个实体类跟我需要的也差不多,我稍微改点也直接用。这样接导致了一种裤子乱穿的问题,久而久之,代码就会变得难以维护。
而在DDD结构中,每一个业务会在自己的model中创建自己需要的实体类,即使和其他业务相似,也不会直接用其他业务创建好的实体类。
但是!如果有些需求需要业务(领域)之间的配合才能完成,该怎么办呢?我们接下来要介绍的应用层,就是为了解决这个问题。
三、应用层
(1) 应用层主要有下面的作用。
下面选取表格中的几项展开说说。
(2)用例编排------对多个领域行为的编排以及和基础服务层的交互。
调用领域层的领域对象(聚合根、实体、服务)完成业务操作,协调跨聚合的交互。这样就解决了领域层之间相互不依赖的局限性的问题。例如订单创建流程中,应用层可能依次调用库存服务、支付服务、物流服务,从而让业务逻辑在应用层交互起来。
@Service
@Transactional
public class BankTransferService {
private final AccountRepository accountRepository;
private final TransferService transferService;
private final AuditService auditService; // 审计领域服务
public BankTransferService(AccountRepository accountRepository,
TransferService transferService,
AuditService auditService) {
// 依赖注入...
}
public void transferMoney(String sourceAccountId, String targetAccountId, BigDecimal amount) {
// 1. 获取源账户和目标账户
Account source = accountRepository.findById(sourceAccountId)
.orElseThrow(() -> new AccountNotFoundException(sourceAccountId));
Account target = accountRepository.findById(targetAccountId)
.orElseThrow(() -> new AccountNotFoundException(targetAccountId));
// 2. 执行转账(调用领域服务)
transferService.transfer(source, target, amount);
// 3. 记录审计日志(调用另一个领域服务)
auditService.log(sourceAccountId, targetAccountId, amount, "转账");
// 4. 保存状态
accountRepository.save(source);
accountRepository.save(target);
}
}
(3)防腐层
它充当一个转换层,将外部系统的概念、数据结构、行为模式等转换为本地系统的领域模型,同时避免外部系统的缺陷或变化直接污染核心领域。
比如:
@Service
public class WeatherAdapter {
public Weather getWeather() {
// 1. 调用外部天气API(返回原始JSON)
String json = httpClient.get("https://weather.com/api/data");
// 2. DTO转换:提取需要的数据
double tempF = parse(json, "$.current.temp_f");
String condition = parse(json, "$.current.condition.text");
// 3. 转换为领域对象(温度转为摄氏度)
return new Weather(tempFToC(tempF), condition);
}
private double tempFToC(double f) { return (f - 32) * 5/9; } // 温度转换
}
当然,防腐也不仅仅局限于DTO的转换,这不是本文的重点,所以就简单陈述一下:
- 隔离变化:防止外部系统的变化影响当前系统的稳定性。
- 保持领域模型的纯洁性:避免外部系统的领域概念污染当前系统的模型。
- 降低耦合:当前系统只依赖于防腐层提供的抽象接口,而不是具体的外部系统。
- 提高可维护性:将转换逻辑集中在一个地方,便于管理和测试。
- 增强测试性:可以通过模拟防腐层来测试业务逻辑,而不需要真正调用外部系统。
对于DDD架构的理解就先描述到这里,后续有机会我想出一篇MVC与DDD架构的对比。
作者水平有限,敬请读者批评指正以及补充!
补充:
DDD(领域驱动设计)是一种以业务领域为核心的软件设计思想与实践方法,强调通过统一语言(Ubiquitous Language)在业务与技术团队之间建立精确沟通,它包含战略设计与战术设计两个层面:战略设计通过划分限界上下文(Bounded Context)处理复杂领域模型间的关系,明确各子领域的边界和交互方式;战术设计则聚焦于单个限界上下文内部的具体实现,定义丰富的领域模型元素如实体(Entity)、值对象(Value Object)、聚合根(Aggregate Root)来封装核心业务规则,并通过领域服务(Domain Service)实现不归属单一对象的逻辑,同时利用仓储(Repository)封装领域对象持久化细节,应用服务(Application Service)则负责协调领域对象、仓储与外部依赖来完成业务用例,最终整个系统遵循分层架构(用户接口层、应用层、领域层、基础设施层)确保领域模型与技术实现分离,其核心目标是让软件结构深刻反映业务本质,降低系统复杂度并提升可维护性与扩展性。