从零到一:使用Spring构建DDD企业级应用的完整实践指南
引言:你还在为DDD落地而困惑吗?
在当今企业级应用开发中,领域驱动设计(Domain-Driven Design, DDD)常被视为解决复杂业务问题的有效方法,却因其陡峭的学习曲线和模糊的实施路径让许多开发者望而却步。你是否也曾面临以下挑战:
- 如何将DDD理论转化为可落地的代码架构?
- 复杂业务逻辑与技术实现如何解耦?
- 微服务与单体架构下的DDD实践有何差异?
- 事件驱动设计与传统CRUD如何协同工作?
本文将通过GitHub加速计划/fac/factory项目(一个基于Spring技术栈的完整DDD企业级应用示例),带你逐步揭开DDD实践的神秘面纱。读完本文后,你将能够:
- 掌握DDD核心概念在实际项目中的应用
- 理解六边形架构(Hexagonal Architecture)的设计与实现
- 学会命令查询职责分离(CQRS)的落地策略
- 构建领域模型与技术实现的边界清晰的应用系统
DDD核心架构:命令查询CRUD职责分离
在传统的应用开发中,我们往往将所有操作都通过CRUD模式处理,导致业务逻辑与数据访问层紧密耦合。而factory项目提出了命令查询CRUD职责分离架构,根据业务复杂度和重要性采用不同的设计策略:
架构决策矩阵
| 业务特征 | 架构策略 | 技术实现 | 示例场景 |
|---|---|---|---|
| 高业务价值、复杂规则 | 领域模型+六边形架构 | 聚合、实体、值对象、领域服务 | 需求调整、库存分析 |
| 复杂数据查询、多维度分析 | 投影查询模型 | 专用查询对象、预计算视图 | 库存报表、交付计划查询 |
| 简单数据操作、低业务规则 | CRUD模式 | 简单DAO、REST资源 | 产品描述管理、文档存储 |
架构演进历程
factory项目的架构并非一蹴而就,而是通过事件风暴(Event Storming)逐步探索和演进:
领域模型设计:从事件风暴到代码实现
需求管理子域(Demand Management)
需求管理是factory项目的核心子域,负责处理产品需求的调整、审核和分析。其核心领域模型如下:
核心领域逻辑实现
以下是ProductDemand聚合根中处理需求调整的核心代码:
public Result adjust(Adjustment adjustment) {
LocalDate date = adjustment.getDate();
if (!dailyDemands.containsKey(date)) {
dailyDemands.put(date, new DailyDemand(date));
}
DailyDemand demand = dailyDemands.get(date);
Result result = demand.adjust(adjustment);
if (reviewPolicy.needsReview(adjustment)) {
Result.requiredReview(new RequiredReview(this.refNo, date, adjustment));
}
domainEvents.publish(new DemandedLevelsChanged(refNo, date, demand.getQuantity()));
return result;
}
库存分析子域(Inventory Analysis)
库存分析子域负责根据需求数据、生产计划和库存情况,分析物料库存状况。其核心计算逻辑如下:
库存计算核心代码
public class InventoryAnalysis {
private final ProductRefNo productRefNo;
private final LocalDate date;
private final int quantity;
private final Stock stock;
private final ProductionForecast production;
private final DeliveriesForecast deliveries;
public static InventoryAnalysis calculate(ProductRefNo product, LocalDate date,
Stock stock, ProductionForecast production,
DeliveriesForecast deliveries, DailyDemand demand) {
int netDemand = demand.getQuantity();
int availableStock = calculateAvailableStock(stock, production, deliveries);
if (netDemand > availableStock) {
return new InventoryAnalysis(product, date, netDemand - availableStock,
stock, production, deliveries);
}
return null;
}
private static int calculateAvailableStock(Stock stock, ProductionForecast production,
DeliveriesForecast deliveries) {
return stock.getCurrentLevel() +
production.getTotalOutput() +
deliveries.getTotalQuantity();
}
}
六边形架构实践:端口与适配器模式
factory项目采用六边形架构,清晰分离领域模型与外部技术实现。以下是项目的核心架构分层:
核心端口与适配器实现
1. 领域事件发布端口
// 端口定义(领域层)
public interface DemandEvents {
void publish(DemandedLevelsChanged event);
void publish(ReviewRequired event);
}
// 适配器实现(基础设施层)
@Service
public class DemandEventsPropagation implements DemandEvents {
private final ApplicationEventPublisher publisher;
@Override
public void publish(DemandedLevelsChanged event) {
publisher.publishEvent(event);
}
@Override
public void publish(ReviewRequired event) {
publisher.publishEvent(event);
// 同时发送到消息队列,供其他限界上下文消费
kafkaTemplate.send("review-required-topic", event);
}
}
2. 仓储端口与ORM实现
// 端口定义(领域层)
public interface ProductDemandRepository {
ProductDemand findByRefNo(String refNo);
void save(ProductDemand productDemand);
List<ProductDemand> findForReview(LocalDate deadline);
}
// 适配器实现(基础设施层)
@Repository
public class ProductDemandORMRepository implements ProductDemandRepository {
private final EntityManager entityManager;
@Override
public ProductDemand findByRefNo(String refNo) {
// JPA查询实现
TypedQuery<ProductDemandEntity> query = entityManager.createQuery(
"SELECT p FROM ProductDemandEntity p WHERE p.refNo = :refNo",
ProductDemandEntity.class);
query.setParameter("refNo", refNo);
return query.getResultStream()
.findFirst()
.map(this::mapToDomain)
.orElseThrow(() -> new ProductDemandNotFoundException(refNo));
}
// 其他方法实现...
}
命令查询职责分离(CQRS)实践
factory项目采用了CQRS思想,将命令操作(修改领域模型)与查询操作(获取数据)分离,采用不同的模型和技术实现。
命令处理流程
查询处理流程
专用查询模型实现
@Service
public class StockAnalysisQuery {
private final ProductDemandRepository demandRepo;
private final ProductionForecastDAO productionDAO;
private final InventoryDAO inventoryDAO;
public StockAnalysis getAnalysis(String productRefNo, LocalDate startDate, LocalDate endDate) {
ProductDemand demand = demandRepo.findByRefNo(productRefNo);
List<ProductionItem> production = productionDAO.findByProductAndDateRange(
productRefNo, startDate, endDate);
StockLevel currentStock = inventoryDAO.getCurrentStock(productRefNo);
// 计算库存分析
StockAnalysisCalculator calculator = new StockAnalysisCalculator(currentStock);
return calculator.calculate(demand, production, startDate, endDate);
}
}
项目实战:环境搭建与运行
开发环境准备
factory项目基于Spring Boot构建,使用Gradle作为构建工具。以下是快速启动项目的步骤:
# 克隆代码仓库
git clone https://gitcode.com/gh_mirrors/fac/factory.git
cd factory
# 构建项目
./gradlew clean build
# 启动应用
./gradlew bootRun
核心功能演示
1. 调整产品需求
# 发送需求调整命令
curl -X POST http://localhost:8080/api/demands/adjust \
-H "Content-Type: application/json" \
-d '{
"productRefNo": "PROD-12345",
"date": "2023-12-15",
"quantity": 150,
"reason": "季度促销活动",
"author": "john.doe@example.com"
}'
2. 查询库存分析
# 查询产品库存分析
curl -X GET "http://localhost:8080/api/stock-analysis?productRefNo=PROD-12345&startDate=2023-12-01&endDate=2023-12-31"
响应结果:
{
"productRefNo": "PROD-12345",
"startDate": "2023-12-01",
"endDate": "2023-12-31",
"dailyAnalysis": [
{"date": "2023-12-01", "demand": 120, "production": 150, "stock": 300},
{"date": "2023-12-02", "demand": 130, "production": 150, "stock": 320},
// ...更多日期数据
{"date": "2023-12-15", "demand": 150, "production": 100, "stock": 180},
// ...更多日期数据
],
"inventoryIssues": [
{"date": "2023-12-20", "quantity": 30, "severity": "MEDIUM"}
]
}
DDD实践经验总结
通过factory项目的实践,我们可以总结出以下DDD实施经验:
成功因素
- 领域专家深度参与:确保业务专家全程参与模型设计和评审
- 渐进式模型演进:通过事件风暴和持续重构逐步完善领域模型
- 明确的边界上下文:清晰划分限界上下文,减少领域间耦合
- 技术与业务分离:通过六边形架构保持领域模型的纯洁性
- 自动化测试:为领域逻辑编写全面的单元测试和集成测试
常见陷阱与解决方案
| 陷阱 | 解决方案 |
|---|---|
| 过度设计领域模型 | 从简单开始,通过重构逐步完善;关注核心业务价值 |
| 贫血领域模型 | 坚持"富领域模型"原则,将业务逻辑封装在领域对象中 |
| 上下文边界模糊 | 使用事件风暴和上下文映射图明确边界;引入防腐层 |
| 技术实现侵入领域 | 严格遵守六边形架构,通过端口和适配器隔离技术细节 |
| 团队DDD知识不足 | 开展内部培训;从小型非核心模块开始实践 |
结语:DDD实践的持续演进
factory项目展示了如何在Spring技术栈上实现DDD架构的企业级应用。通过命令查询CRUD职责分离、六边形架构和领域驱动设计的结合,我们可以构建出既满足复杂业务需求,又具备良好可维护性和可扩展性的系统。
DDD不是一个一次性的项目,而是一个持续演进的过程。随着业务的发展和团队对领域理解的深入,领域模型也需要不断重构和优化。希望factory项目能够为你的DDD实践提供有益的参考和启示。
如果你觉得本文对你有帮助,请点赞、收藏并关注作者,以获取更多关于DDD和企业级应用架构的实践文章。下期我们将深入探讨事件溯源(Event Sourcing)在DDD项目中的应用。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



