DDD架构Repository仓储数据流转全链路详解:Domain与其他层的数据流转

目录

    • [1. 引言:为什么需要关注数据流转?](#1. 引言:为什么需要关注数据流转?)
    • [2. 整体流程概览](#2. 整体流程概览)
    • [3. 逐节点详细流转分析](#3. 逐节点详细流转分析)
      • [3.1 起点:Domain 层发起调用](#3.1 起点:Domain 层发起调用)
      • [3.2 进入Infrastructure层下的 Repository 实现:`AgentRepository.queryAvailableAgents()`](#3.2 进入Infrastructure层下的 Repository 实现:AgentRepository.queryAvailableAgents())
      • [3.3 进入 Infrastructure 层:MyBatis 代理执行](#3.3 进入 Infrastructure 层:MyBatis 代理执行)
      • [3.4 回到 Repository 层:进行数据转换与聚合](#3.4 回到 Repository 层:进行数据转换与聚合)
      • [3.5 终点:Domain 层接收并消费数据](#3.5 终点:Domain 层接收并消费数据)
    • [4. 数据形态演变全览](#4. 数据形态演变全览)
    • [5. 核心设计思想与最佳实践](#5. 核心设计思想与最佳实践)
    • [6. 总结](#6. 总结)

1. 引言:为什么需要关注数据流转?

在分层架构(如 DDD、Clean Architecture)中,Repository 是连接领域层(Domain)与数据层(Infrastructure)的核心枢纽。理解一次查询请求中,数据如何从 Domain 层出发,经过层层转换,最终又以 Domain 层可理解的形态返回,是掌握系统架构与性能优化的关键。

本文将以一个典型的 AgentRepository.queryAvailableAgents() 方法为例,逐行、逐节点剖析数据从 Domain 层出发,到返回 Domain 层的完整流转过程。

2. 整体流程概览

bash 复制代码
graph TD
    A[Domain Layer: 调用 repository.queryAvailableAgents()] --> B[Repository Layer: AgentRepository]
    B --> C[① 调用 DAO 接口]
    C --> D[Infrastructure Layer: MyBatis 代理介入]
    D --> E[② 执行 SQL 查询]
    E --> F[③ JDBC 返回 ResultSet]
    F --> G[④ ResultSet 映射为 PO 列表]
    G --> H[⑤ 接收 List<AiAgent> PO 列表]
    H --> I[⑥ 遍历 PO,转换为 VO]
    I --> J[⑦ 返回 List<AiAgentVO>]
    J --> K[Domain Layer: 接收 VO 列表,继续业务逻辑]

核心路径:Domain → Repository → DAO/MyBatis → Database → ResultSet → PO → VO → Domain。

3. 逐节点详细流转分析

流程对应

bash 复制代码
① domain层Service需要查询数据
   │  例如:ArmoryService.acceptArmoryAgent() 需要查询智能体配置
   │
   ↓
② Service调用仓储接口
   │  IAgentRepository repository;
   │  repository.queryAvailableAgents();
   │
   ↓
③ 进入AgentRepository实现
   │  aiAgentDao.queryEnabledAgents();
   │
   ↓
④ MyBatis动态代理(Mybatis)
   │  找到XML中对应的SQL
   │
   ↓
⑤ 执行SQL(Mybatis)
   │  SELECT * FROM ai_agent WHERE status = 1
   │
   ↓
⑥ JDBC返回ResultSet(Mybatis)
   │  表格形式的原始数据
   │
   ↓
⑦ resultMap映射(Mybatis)
   │  column="agent_id" → property="agentId"
   │  column="agent_name" → property="agentName"
   │  ...
   │
   ↓
⑧ 填充PO对象(Repository里实现的接口)
   │  List<AiAgent> aiAgents
   │
   ↓
⑨ PO转VO(Repository里实现的接口)
   │  aiAgent.getAgentId() → vo.agentId
   │  aiAgent.getAgentName() → vo.agentName
   │  ...
   │
   ↓
⑩ 返回List<AiAgentVO>给domain层Service(Repository里实现的接口)

代码流程详解

bash 复制代码
// ① domain层Service
@Service
public class ArmoryService implements IArmoryService {

    @Resource
    private IAgentRepository repository;

    @Override
    public void acceptArmoryAgent(String agentId) {
        // ② 调用仓储接口查询数据
        List<AiAgentClientFlowConfigVO> configs = repository.queryAiAgentClientsByAgentId(agentId);
    }
}

// ③ 仓储实现
@Repository
public class AgentRepository implements IAgentRepository {

    @Resource
    private IAiAgentDao aiAgentDao;

    @Override
    public List<AiAgentVO> queryAvailableAgents() {
        // ④ 调用DAO接口,触发MyBatis动态代理
        List<AiAgent> aiAgents = aiAgentDao.queryEnabledAgents();
        //   ↑
        //   │  ⑤ MyBatis找到XML中的SQL并执行
        //   │     SELECT * FROM ai_agent WHERE status = 1
        //   │
        //   │  ⑥ JDBC返回ResultSet(表格数据)
        //   │
        //   │  ⑦ 根据resultMap映射 column → property
        //   │     column="agent_id" → property="agentId"
        //   │     column="agent_name" → property="agentName"
        //   │
        //   │  ⑧ 反射创建AiAgent对象,调用setter赋值
        //   │     aiAgent.setAgentId("45855306")
        //   │     aiAgent.setAgentName("测试智能体")
        //   │
        //   ↓  返回 List<AiAgent>(PO列表)

        // ⑨ PO转VO
        List<AiAgentVO> aiAgentVOS = new ArrayList<>();
        for (AiAgent aiAgent : aiAgents) {
            aiAgentVOS.add(AiAgentVO.builder()
                    .agentId(aiAgent.getAgentId())
                    .agentName(aiAgent.getAgentName())
                    .build());
        }

        // ⑩ 返回VO
        return aiAgentVOS;
    }
}



<!-- ⑤⑥⑦⑧ 这些步骤发生在MyBatis内部 -->
<select id="queryEnabledAgents" resultMap="AiAgentMap">
    SELECT id, agent_id, agent_name, description, channel, strategy, status, create_time, update_time
    FROM ai_agent
    WHERE status = 1
    ORDER BY create_time DESC
</select>

<!-- ⑦ resultMap定义映射关系 -->
<resultMap id="AiAgentMap" type="cn.moonice.infrastructure.dao.po.AiAgent">
    <id column="id" property="id"/>
    <result column="agent_id" property="agentId"/>
    <result column="agent_name" property="agentName"/>
    <result column="description" property="description"/>
    <result column="channel" property="channel"/>
    <result column="strategy" property="strategy"/>
    <result column="status" property="status"/>
    <result column="create_time" property="createTime"/>
    <result column="update_time" property="updateTime"/>
</resultMap>

3.1 起点:Domain 层发起调用

位置 :领域模型的仓储服务
代码示意

java 复制代码
    @Resource
    private IAgentRepository repository;

// 在某个 Domain Re或 Application Service 中
List<AiAgentVO> aiAgentVOS = repository.queryAvailableAgents();
// 接下来基于 VO 进行领域逻辑计算,如:过滤、排序、组合业务规则等
java 复制代码
# repository可以调用infrastructure层实现的IAgentRepository方法原因

Spring启动时扫描所有模块
        │
        ↓
发现 IAgentRepository 接口(domain层)
        │
        ↓
发现 AgentRepository 实现了 IAgentRepository(infrastructure层)
        │
        ↓
AgentRepository 有 @Repository 注解,注册为Spring Bean
        │
        ↓
ArmoryService 中 @Resource IAgentRepository repository
        │
        ↓
Spring自动把 AgentRepository 注入到 repository 字段
        │
        ↓
调用 repository.queryAvailableAgents() 实际执行的是 AgentRepository 的实现

数据形态 :此时尚未有具体数据,只是一个方法调用契约。Domain 层期望得到一个 List<AiAgentVO>(值对象列表),用于后续的纯领域逻辑。

3.2 进入Infrastructure层下的 Repository 实现:AgentRepository.queryAvailableAgents()

节点 ①:调用 DAO 接口

java 复制代码
List<AiAgent> aiAgents = aiAgentDao.queryEnabledAgents();
  • 作用:Repository 作为防腐层,调用基础设施层的具体数据访问对象(DAO)。
  • 数据形态转换 :方法签名从返回 List<AiAgentVO>(Domain 所需)转换为操作 List<AiAgent>(持久化对象,与数据库表结构对齐)。
  • 关键点 :这里开始了 Domain ModelPersistence Model 的分离。

3.3 进入 Infrastructure 层:MyBatis 代理执行

节点 ②:MyBatis 动态代理介入

  • aiAgentDao 是一个 MyBatis Mapper 接口。
  • MyBatis 在运行时通过动态代理,将接口方法调用 queryEnabledAgents() 绑定到对应的 SQL 映射语句。

节点 ③:执行 SQL 查询

sql 复制代码
-- 对应的 SQL 映射(通常在 XML 或注解中)
SELECT agent_id, agent_name, description, channel, strategy, status 
FROM ai_agent 
WHERE status = 1;
  • SQL 经由连接池发送到数据库。
  • 数据形态:从数据库的二进制存储格式,转换为网络传输的字节流。

节点 ④:JDBC 返回 ResultSet

  • 数据库执行查询,通过 JDBC 驱动将结果以 ResultSet 对象的形式返回给 MyBatis。
  • ResultSet 是一个游标,指向查询结果集的第一行之前。
  • 数据形态:此时数据是原始的、按列组织的二维表结构,带有数据库元数据(如列名、类型)。

节点 ⑤:ResultSet 映射,填充 PO(关键映射步骤)

  • MyBatis 根据 <resultMap> 配置,将 ResultSet 的每一行映射为一个 AiAgent POJO 实例。
xml 复制代码
<!-- 示例 resultMap -->
<resultMap id="AiAgentResult" type="com.example.infrastructure.po.AiAgent">
    <id column="agent_id" property="agentId"/>
    <result column="agent_name" property="agentName"/>
    <result column="description" property="description"/>
    <result column="channel" property="channel"/>
    <result column="strategy" property="strategy"/>
    <result column="status" property="status"/>
</resultMap>
  • 映射过程(以第一行为例)
    1. ResultSet 游标移动到第一行。
    2. 读取 column="agent_id" 的值,例如 "45855306"
    3. 调用 aiAgent.setAgentId("45855306")
    4. 循环处理所有列,完成一个 AiAgent 对象的属性填充。
    5. 将该对象加入 List<AiAgent>
    6. 游标下移,重复直到 ResultSet 结束。
  • 数据形态转换ResultSet(原始表数据) → AiAgent PO(带有业务语义的 Java 对象)。

3.4 回到 Repository 层:进行数据转换与聚合

节点 ⑥:Repository 接收 PO 列表

java 复制代码
// aiAgents 现在是一个包含所有查询结果的 List<AiAgent>
List<AiAgent> aiAgents = aiAgentDao.queryEnabledAgents(); // 承接上一步的结果
  • 数据形态List<AiAgent>,此时数据已脱离 JDBC 原始形态,是完整的 Java 对象集合。

节点 ⑦:PO 转换为 VO(领域值对象)

java 复制代码
List<AiAgentVO> aiAgentVOS = new ArrayList<>();
for (AiAgent aiAgent : aiAgents) {
    aiAgentVOS.add(AiAgentVO.builder()
            .agentId(aiAgent.getAgentId())
            .agentName(aiAgent.getAgentName())
            .description(aiAgent.getDescription())
            .channel(aiAgent.getChannel())
            .strategy(aiAgent.getStrategy())
            .status(aiAgent.getStatus())
            .build());
}
  • 转换目的
    1. 隔离变化:数据库表结构(PO)变化不影响 Domain 层。
    2. 语义丰富:VO 可以包含衍生字段、计算属性,更贴合领域语言。
    3. 序列化友好:VO 通常设计为用于网络传输或前端展示。
  • 数据形态转换AiAgent PO(持久化对象) → AiAgentVO(领域值对象)。字段值被复制,但对象语义和用途发生变化。

节点 ⑧:返回 VO 列表给 Domain 层

java 复制代码
return aiAgentVOS;
  • 至此,Repository 完成了其核心职责:为 Domain 层提供透明的、领域语义的数据访问

3.5 终点:Domain 层接收并消费数据

  • Domain 层拿到 List<AiAgentVO>,完全不知道这些数据来自 MySQL、Redis 还是任何其他数据源。
  • Domain 层可以基于这些 VO 进行纯业务逻辑处理,例如:
java 复制代码
// 领域逻辑示例:过滤出特定渠道的智能体
List<AiAgentVO> availableAgents = agentRepository.queryAvailableAgents();
List<AiAgentVO> webChannelAgents = availableAgents.stream()
        .filter(vo -> "WEB".equals(vo.getChannel()))
        .collect(Collectors.toList());
// 或者进行业务规则校验、组合聚合等复杂操作
  • 数据形态AiAgentVO 作为 Domain 层内部流通的"货币",其生命周期仅限于本次业务操作,通常不持久化。

4. 数据形态演变全览

阶段 位置 数据形态 代表对象 特点与职责
存储 数据库磁盘 二进制行记录 ai_agent 持久化存储,遵循数据库范式
传输 JDBC 网络流 字节流 / ResultSet ResultSet 原始查询结果,带元数据
持久化对象 Infrastructure 层 Java POJO AiAgent 与表结构严格映射,包含数据库注解
领域值对象 Repository/Domain 层 不可变值对象 AiAgentVO 承载领域语义,用于业务逻辑流转
消费 Domain 层 业务对象集合 List<AiAgentVO> 领域逻辑操作的输入/输出

关键转换点

  1. ResultSet → PO :由 MyBatis resultMap 完成,是 数据库世界到 Java 对象世界 的桥梁。
  2. PO → VO :由 Repository 手动或使用工具(如 MapStruct)完成,是 持久化模型到领域模型 的转换。

5. 核心设计思想与最佳实践

  1. 单一职责:Repository 只负责数据访问与初步聚合,不包含业务逻辑。
  2. 依赖倒置:Domain 层依赖 Repository 接口,而非具体实现(如 MyBatis)。
  3. 模型隔离:PO(持久化对象)与 VO/DTO(领域/传输对象)严格分离,避免数据库 schema 污染领域模型。
  4. 性能考量
    • N+1 查询问题 :在 resultMap 中使用 <collection><association> 实现关联查询,避免在循环中查询数据库。
    • 字段冗余:VO 仅包含 Domain 层需要的字段,避免大对象网络传输。
    • 转换开销:大量数据转换时,考虑使用 MapStruct 等编译期代码生成工具提升性能。

6. 总结

一次 domain层service的queryAvailableAgents() 调用,看似简单,实则完成了一次跨越 Domain层、Repository层、Infrastructure层、数据库 的完整数据旅程。

  • 起点与终点都是 Domain 层,体现了"领域驱动"的核心。
  • Repository 是关键的协调者,它屏蔽了底层数据获取的复杂性,并为 Domain 层提供了整洁的、富含领域语义的接口。
  • 理解每一步的数据形态转换,有助于我们定位性能瓶颈(如过度转换)、设计更清晰的模型(如 PO/VO 分离),并构建更健壮、可维护的系统架构。

作者基于代码数据流程拆解,梳理ddd架构下的流转过程,也希望对读者有帮助。

相关推荐
吴声子夜歌1 小时前
Java——类加载机制
java·开发语言·python
Xiacqi11 小时前
Java 中 String、StringBuffer、StringBuilder 的区别
java
Xiacqi11 小时前
Java 常用集合框架手册
java
笨蛋不要掉眼泪2 小时前
Java并发编程:线程的创建和运行
java·开发语言·jvm
九伯都2 小时前
java编写 agent 入门案例
java·开发语言
环流_2 小时前
redis:持久化rdb
java·数据库·redis
xqqxqxxq2 小时前
Java 线程池(一)
java·开发语言
Full Stack Developme2 小时前
spring-beans 解析
java·后端·spring
码农-阿杰3 小时前
生成偏向锁 + JIT
java