一、项目结构详解(以电商订单系统为例)
bash
src/main/java
├── com.example
│ ├── common # 通用工具类、基础异常、常量
│ ├── order # 订单限界上下文(模块示例)
│ │ ├── interfaces # 用户接口层
│ │ │ ├── controller # HTTP/REST API
│ │ │ ├── rpc # Dubbo/gRPC 接口
│ │ │ └── consumer # 消息队列消费者(如Kafka监听)
│ │ ├── application # 应用层
│ │ │ ├── service # 应用服务(流程编排)
│ │ │ ├── dto # 入参/出参对象(适配外部协议)
│ │ │ └── event # 应用事件(如发送邮件通知)
│ │ ├── domain # 领域层(核心业务逻辑)
│ │ │ ├── model # 领域模型
│ │ │ │ ├── aggregate # 聚合根(如Order)
│ │ │ │ ├── entity # 实体(如OrderItem)
│ │ │ │ └── vo # 值对象(如Address)
│ │ │ ├── service # 领域服务(跨聚合逻辑)
│ │ │ ├── event # 领域事件(如OrderPaidEvent)
│ │ │ ├── repository # 仓储接口(抽象定义)
│ │ │ └── spec # 规约模式(动态查询条件)
│ │ └── infrastructure # 基础设施层
│ │ ├── persistence # 持久化实现
│ │ │ ├── entity # 数据库实体(JPA/MyBatis)
│ │ │ ├── converter # 领域对象与持久化对象转换器
│ │ │ └── repository # 仓储实现(如OrderRepositoryImpl)
│ │ ├── client # 外部服务调用(支付、库存)
│ │ └── mq # 消息队列生产者
│ └── user # 用户限界上下文(其他模块)
└── resources
├── config # 配置文件
└── scripts # 数据库脚本
二、分层依赖关系与设计原则
1. 依赖方向(严格单向)
- 用户接口层 → 应用层 → 领域层 ← 基础设施层
- 领域层是核心:不依赖任何其他层(无框架注解、无数据库依赖)。
2. 分层职责对比(表格)
分层 | 职责 | 技术选型 | 关键输出 |
---|---|---|---|
用户接口层 | 处理 HTTP/RPC/消息,参数校验,DTO 转换 | Spring MVC、Dubbo、Kafka | Controller、DTO、消息监听类 |
应用层 | 事务管理、权限校验、领域逻辑编排 | Spring Transaction、AspectJ | 应用服务类、应用事件 |
领域层 | 实现核心业务规则,定义聚合根、实体 | 纯 Java(无框架依赖) | 聚合根、领域事件、领域服务 |
基础设施层 | 数据库访问、消息发送、外部服务调用 | JPA、MyBatis、Redis、Kafka | 仓储实现、防腐层、消息生产者 |
三、分层职责与实现案例
1. 用户接口层(interfaces)
-
职责 :
- 接收并校验外部请求(HTTP/RPC/消息)。
- 将外部参数转换为应用层 DTO。
-
示例 :
java@RestController @RequestMapping("/orders") public class OrderController { @Autowired private OrderAppService appService; @PostMapping public OrderResponse createOrder(@RequestBody OrderRequest request) { // 参数校验(如商品ID合法性) ValidateUtils.checkValid(request); // 调用应用层服务 return appService.createOrder(request); } }
-
落地难点 :
- DTO 膨胀:不同协议(HTTP/RPC)需定义多套 DTO,增加维护成本。
- 解决方案:使用 MapStruct 等工具自动转换 DTO。
2. 应用层(application)
-
职责 :
- 编排领域对象,管理事务、日志、安全等横切关注点。
- 发布应用事件(如发送邮件、短信)。
-
示例 :
java@Service public class OrderAppService { @Autowired private OrderRepository orderRepository; @Autowired private DomainEventPublisher eventPublisher; @Transactional public OrderResponse createOrder(OrderRequest request) { // 1. 调用领域层创建聚合根 Order order = OrderFactory.create(request); // 2. 持久化聚合根 orderRepository.save(order); // 3. 发布领域事件(如触发库存扣减) eventPublisher.publish(new OrderCreatedEvent(order.getId())); // 4. 返回响应 return OrderConverter.toResponse(order); } }
-
落地难点 :
- 事务边界过长:应用层事务可能包含多个领域操作,导致锁竞争。
- 解决方案:拆分为小事务 + 最终一致性(Saga 模式)。
3. 领域层(domain)
-
职责:
- 聚合根:管理实体和值对象,封装业务规则(如订单状态机)。
- 领域服务:处理跨聚合逻辑(如订单价格计算依赖促销规则)。
-
示例:
java// 聚合根(封装核心业务逻辑) public class Order implements AggregateRoot<Long> { private Long id; private OrderStatus status; private List<OrderItem> items; // 业务方法:支付订单 public void pay(Payment payment) { if (status != OrderStatus.CREATED) { throw new IllegalOrderStateException("Order must be created"); } if (!payment.validate()) { throw new PaymentFailedException("Invalid payment"); } status = OrderStatus.PAID; registerEvent(new OrderPaidEvent(id)); // 记录领域事件 } }
-
落地难点:
- 贫血模型陷阱:开发习惯将逻辑写在 Service 而非聚合根中。
- 解决方案:代码审查 + 静态检查工具(如 ArchUnit)约束。
-
归属对象:聚合根(Aggregate Root)、实体(Entity)、值对象(Value Object)。
-
设计原则:
- 高内聚:实体和值对象必须封装业务逻辑(如状态校验、计算规则)。
- 无框架依赖 :禁止引入 Spring/JPA 等框架注解(如
@Entity
)。
-
示例代码:
java// 领域实体(属于聚合根的一部分) public class OrderItem { private ProductId productId; // 值对象 private Integer quantity; private Money price; // 值对象(金额+货币单位) // 业务逻辑:计算商品总价 public Money calculateTotal() { return price.multiply(quantity); } } // 聚合根(订单的核心管理边界) public class OrderAggregate implements AggregateRoot<OrderId> { private OrderId id; private OrderStatus status; private List<OrderItem> items; private Address address; // 值对象 // 核心业务逻辑:支付订单 public void pay(Payment payment) { if (status != OrderStatus.CREATED) { throw new IllegalOrderStateException("Only created orders can be paid"); } this.status = OrderStatus.PAID; registerEvent(new OrderPaidEvent(this.id, payment)); // 记录领域事件 } }
4. 基础设施层(infrastructure)
-
职责:
- 实现领域层定义的接口(如仓储、消息发送)。
- 封装外部服务调用(如支付、物流),防止污染领域层。
-
示例:
java// 仓储实现(数据库操作) @Repository public class OrderRepositoryImpl implements OrderRepository { @Autowired private OrderJpaRepository jpaRepository; @Override public Order findById(Long id) { OrderEntity entity = jpaRepository.findById(id).orElseThrow(); return OrderConverter.toDomain(entity); // 转换领域对象 } } // 防腐层(隔离第三方支付接口) @Component public class PaymentClient { public PaymentResult pay(OrderPaymentCommand command) { ThirdPartyResponse response = callThirdParty(command); return PaymentConverter.toResult(response); // 转换领域模型 } }
-
落地难点:
- 数据库与领域模型不匹配:如值对象需序列化存储。
- 解决方案:使用 JSON 字段或 NoSQL 数据库。
-
归属对象:数据库实体(与 ORM 框架绑定的对象)、DAO 实现。
-
设计原则:
- 与领域模型隔离:数据库实体是技术细节,不应影响领域层。
- 转换器模式 :通过
Converter
类实现领域对象与数据库实体的双向转换。
-
示例代码:
java// 数据库实体(JPA 注解) @Entity @Table(name = "orders") public class OrderEntity { @Id private Long id; private String status; @Column(name = "address_json") private String addressJson; // 值对象序列化为 JSON } // 转换器(领域对象 ↔ 数据库实体) public class OrderConverter { public static OrderAggregate toDomain(OrderEntity entity) { Address address = JsonUtils.fromJson(entity.getAddressJson(), Address.class); return OrderAggregate.builder() .id(new OrderId(entity.getId())) .status(OrderStatus.valueOf(entity.getStatus())) .address(address) .build(); } public static OrderEntity toEntity(OrderAggregate order) { OrderEntity entity = new OrderEntity(); entity.setId(order.getId().getValue()); entity.setStatus(order.getStatus().name()); entity.setAddressJson(JsonUtils.toJson(order.getAddress())); return entity; } }
防腐层设计
-
在
infrastructure/client
中隔离外部服务依赖:java// 基础设施层:调用支付服务(防止领域层污染) @Component public class PaymentClient { @Autowired private ThirdPartyPaymentService paymentService; // 将第三方返回结果转换为领域对象 public PaymentResult pay(OrderPaymentCommand command) { ThirdPartyResponse response = paymentService.invoke(command); return PaymentResultConverter.fromResponse(response); } }
领域实体/值对象/数据库实体/DTO关键设计区别
对象类型 | 所属分层 | 职责 | 技术依赖 |
---|---|---|---|
领域实体 | 领域层(domain) | 封装业务逻辑和状态变更 | 无框架依赖 |
值对象 | 领域层(domain) | 描述不可变属性(如地址、金额) | 无框架依赖 |
数据库实体 | 基础设施层(infra) | 映射数据库表结构 | 依赖 JPA/MyBatis 等 |
DTO | 应用层(application) | 传输数据,适配外部接口 | 可包含 Jackson 注解 |
常见问题解答
Q1: 为什么数据库实体和领域实体要分离?
- 答案 :避免 ORM 框架侵入领域逻辑。例如:
- 领域实体
OrderAggregate
包含业务方法pay()
。 - 数据库实体
OrderEntity
只包含 JPA 注解和表结构映射。
- 领域实体
Q2: 值对象如何持久化到数据库?
-
方案 :序列化为 JSON 或拆分到多个字段:
java// 值对象(领域层) public class Address { private String province; private String city; private String detail; } // 数据库实体(基础设施层) @Entity public class OrderEntity { @Column(name = "address_json") private String addressJson; // 存储为 JSON 字符串 }
Q3: 聚合根如何管理子实体?
-
规则 :通过聚合根的方法操作子实体,禁止直接访问:
javapublic class OrderAggregate { private List<OrderItem> items = new ArrayList<>(); // 外部必须通过聚合根方法操作 items public void addItem(ProductId productId, int quantity) { items.add(new OrderItem(productId, quantity)); } }
最终总结
- 领域层 :放置 聚合根、实体、值对象,承载核心业务规则。
- 基础设施层 :放置 数据库实体、转换器,处理技术细节。
- 严格分层:通过依赖倒置(DIP)确保领域层不被污染。
四、落地难点与阻碍
1. 技术层面的挑战
问题 | 解决方案 |
---|---|
聚合根过大导致性能问题 | 拆分聚合 + CQRS 分离读写模型 |
领域事件丢失或重复消费 | 消息队列事务 + 消费者幂等设计 |
复杂查询性能低下 | 单独构建读模型(Elasticsearch 物化视图) |
2. 团队协作阻力
- 问题:业务方不理解领域模型,开发人员抵触 DDD 设计成本。
- 解决方案 :
- 用实际案例证明 DDD 长期收益(如需求交付效率提升 40%)。
- 通过事件风暴(Event Storming)工作坊对齐业务与技术认知。
3. 架构演进成本
- 问题:遗留系统改造困难,数据库表结构无法直接映射领域模型。
- 解决方案 :
- 渐进式改造:优先重构核心业务模块。
- 双写策略:新旧模型并行,逐步迁移数据。
五、总结
DDD 分层架构通过明确的职责划分,将业务复杂度与技术复杂度解耦。其核心价值在于:
- 业务聚焦:领域层成为业务规则的唯一真相源。
- 技术隔离:基础设施层灵活替换不影响核心逻辑。
- 团队协作:统一语言降低沟通成本。
实际落地中需注意:避免过度设计 (简单 CRUD 系统无需 DDD),接受阶段性不完美,并通过自动化测试和代码规范保障架构一致性。
六、常见问题解答(FAQ)
Q1: 为什么数据库实体(JPA Entity)和领域实体(Domain Entity)要分离?
- 问题背景:开发者习惯直接使用 JPA 注解的类作为业务模型,导致数据库技术细节侵入领域逻辑。
- 解答 :
-
职责分离:领域实体封装业务规则(如订单状态流转),数据库实体仅描述表结构。
-
技术解耦:领域层不依赖 JPA/Hibernate 等框架,保持核心逻辑纯净。
-
示例对比 :
java// ❌ 错误做法:领域模型被 JPA 污染 @Entity public class Order { @Id private Long id; @Column(name = "status") private String status; // 直接暴露数据库字段 // 业务逻辑混杂在数据对象中 public void pay() { ... } } // ✅ 正确做法:领域模型与数据库实体分离 // 领域层(无框架依赖) public class Order { private OrderId id; private OrderStatus status; public void pay() { ... } // 纯业务逻辑 } // 基础设施层(数据库实体) @Entity @Table(name = "orders") public class OrderEntity { @Id private Long id; private String status; // 数据库字段名可自由定义 }
-
Q2: 值对象(如 Address)如何持久化到数据库?
- 问题背景:值对象无唯一标识且不可变,直接映射到关系型数据库存在挑战。
- 解决方案 :
-
序列化为 JSON :
java// 领域层:值对象 public class Address { private String province; private String city; private String detail; } // 基础设施层:数据库实体 @Entity public class OrderEntity { @Column(name = "address_json") private String addressJson; // 存储为 JSON 字符串 } // 转换器:领域对象 ↔ 数据库实体 public class OrderConverter { public static Address toAddress(String json) { return JsonUtils.fromJson(json, Address.class); } }
-
拆分为多字段 :
sql-- 数据库表设计 CREATE TABLE orders ( id BIGINT PRIMARY KEY, province VARCHAR(50), city VARCHAR(50), address_detail VARCHAR(100) );
-
使用 NoSQL:对复杂值对象(如嵌套结构),直接存入文档数据库(MongoDB)。
-
Q3: 聚合根如何管理子实体(如 Order 管理 OrderItem)?
-
问题背景:开发者可能绕过聚合根直接操作子实体,破坏封装性。
-
规则与示例 :
-
禁止外部直接访问子实体:所有操作必须通过聚合根方法。
-
示例代码 :
javapublic class Order implements AggregateRoot<Long> { private List<OrderItem> items = new ArrayList<>(); // 外部必须通过聚合根添加商品 public void addItem(ProductId productId, int quantity) { if (this.status != OrderStatus.DRAFT) { throw new IllegalOrderStateException("Cannot modify order"); } items.add(new OrderItem(productId, quantity)); } // 禁止直接暴露 items 集合 public List<OrderItem> getItems() { return Collections.unmodifiableList(items); } }
-
-
设计意义 :
- 一致性保障:聚合根确保子实体的状态变更符合业务规则。
- 事务边界明确:一个聚合对应一个事务单元,避免分布式事务复杂性。
七、总结:DDD 不是架构,而是思维方式
DDD 分层架构的核心目标是通过职责分离 和统一语言,让业务逻辑与技术实现解耦。其难点不在于技术,而在于团队能否:
- 接受前期设计成本:领域建模需要业务与技术的深度协作。
- 抵御惯性思维:避免将 DDD 退化为"高级三层架构"。
- 持续演进:领域模型需随业务迭代更新,而非一次性设计。
最终,DDD 的成败取决于团队是否愿意让代码反映业务,而非让业务适应代码。