第4章 数据模型与领域模型分离:ORM映射策略、DTO/PO/DO转换、解耦规则

4.1 问题背景与核心概念

在分层架构中,数据持久化模型(PO/Persistence Object)与领域模型(DO/Domain Object)往往具有不同的生命周期与职责。直接复用同一模型会导致:

  • 数据库结构变更直接污染领域逻辑
  • ORM 注解/配置侵入领域类,破坏业务纯净性
  • 序列化/反序列化约束影响对象设计(例如 JSON 字段与表字段耦合)

本章定义三种核心模型的边界与转换策略:

模型全称职责归属层
POPersistence Object直接映射数据库表(列、关联、主键)数据层(Repository 内部)
DODomain Object体现业务概念、行为、不变量领域层(Domain 层)
DTOData Transfer Object跨层/跨进程传输,按需求裁剪字段应用层/接口层
关键原则:依赖方向永远由外层指向内层,PO、DO、DTO 之间通过显式转换器(Converter)解耦,不允许跨层直接引用对方的内部模型。

4.2 边界与分层

数据层与领域层之间的边界由 Repository 接口 和 ORM 映射策略共同定义。Repository 接口返回 DO,隐藏 PO 的存在;ORM 框架负责将 PO 转化为 DO,或将 DO 持久化为 PO。典型的分层结构如下:

数据模型与领域模型分离:分层与转换关系 领域层(Domain) DO(Domain Object) 业务行为 / 不变量 Repository 接口 转换层(Converter) PO ←→ DO DO ←→ DTO 应用层(Application) DTO(数据传输对象) 应用服务 数据层(Infrastructure) Repository 实现 PO(ORM 实体) 依赖 方向:外向→内向
信任链路:领域层只信任 Repository 接口返回的 DO,不信任 PO;应用层只信任领域层,不直接操作 Repository 实现。Converter 是唯一被允许跨层转换的组件。

4.3 ORM 映射策略选型

主流策略包括全自动映射(JPA/Hibernate)、半自动映射(MyBatis)和手动映射(Spring JDBC Template)。选型权衡如下:

策略映射方式灵活度学习成本典型风险
全自动(JPA)注解/XML 定义关系,自动生成 SQL低高N+1 查询、隐式延迟加载陷阱
半自动(MyBatis)手写 SQL + 结果映射高中SQL 分散、PO 与 DO 转换仍需手动
手动(JDBC Template)完全手写 SQL 与 RowMapper最高低重复代码多、易出错
风险提示:全自动映射如果未正确配置懒加载/急加载,会导致 N+1 查询性能问题。半自动映射容易导致 PO 与 DO 字段过度耦合(例如直接使用 PO 作为 DO)。手动映射虽然可控,但团队维护成本高。

4.4 DTO/PO/DO 转换落地步骤

  1. 定义模型接口契约
    在领域层定义 DO 和 Repository 接口;在数据层定义 PO 和 Repository 实现;在应用层定义 DTO。
  2. 选择转换方式
    首选编译期工具(如 MapStruct)避免运行时反射开销;次选手动 Converte;避免对 DO 直接添加 getter/setter 以外的 ORM 注解。
  3. 定义转换接口
    使用伪代码定义 Converter:
// 领域层:Converter 接口
public interface Converter<P, D> {
    D toDomain(P po);
    P toPO(D domain);
}

// 数据层实现:XXXConverter implements Converter<UserPO, UserDO> {
    // 字段复制 + 规则转换
}
  • 4. 隔离 P O 依赖:禁止任何领域层或应用层代码 import 数据层 PO 类。
  • 5. 单元测试覆盖:验证所有字段映射正确性,尤其处理 null、嵌套对象、枚举。

4.5 验收标准

维度验收条件
模型纯净度领域层 DO 不含 @Entity、@Table 等 ORM 注解;PO 不含业务方法
转换独立性转换器单元测试通过;字段映射覆盖率达到 100%
循环依赖检测编译期工具(如 ArchUnit)验证无跨层直接引用 PO
性能转换耗时占总请求时间 < 5%(基准测试)
可维护性新增字段只需修改 PO/DO 定义和 Converter,无需改动上层逻辑
落地最佳实践:在持续集成中加入 ArchUnit 规则,强制禁止 import 数据层 PO 到领域层;Converter 使用 MapStruct,自动生成映射代码,减少人工维护成本。

4.6 风险点与应对措施

  • 模型膨胀:PO/DO/DTO 三者字段高度相似时,团队容易放弃分离。应对:坚持“一个模型一个职责”原则,通过代码审查保证。
  • 延迟加载失效:在 Converter 中访问 PO 的延迟加载属性可能导致异常。应对:在 Repository 实现中限定时查询加载策略(JOIN FETCH)。
  • 事务边界模糊:PO 的事务状态透传到领域层。应对:所有 PO 操作必须在数据层事务内完成,领域层只操作已加载的 DO。

通过上述策略,数据模型与领域模型的分离不再是理论口号,而是可度量、可验证的架构纪律。