Chat2DB Java 服务端对象转换契约:Converter 集中式映射规范与源码落地实践
【免费下载链接】Chat2DBChat2DB is a free, cross-platform, local-first database client and SQL workspace for developers, DBAs, analysts, and data teams. Connect to 40+ databases, manage data, edit and run SQL, and use your own AI model to generate, explain, and optimize queries. Available on desktop, web, Docker, and CLI, with MCP support.项目地址: https://gitcode.com/GitHub_Trending/ch/Chat2DB
导读
本文以 Chat2DB 仓库中 spec/code/server/java-object-converter-contracts.md 为骨架,系统讲解服务端对象转换(Object Conversion)必须集中到 Converter 的工程规范:包括转换的定义与边界、各模块 Converter 归属表、禁止的临时转换写法、纯映射(Pure-Mapping)规则、调用边界、允许的例外场景以及评审清单。读者学完本文,能够理解 Chat2DB 多模块(web / domain-core / storage / spi / plugins / tools)之间为何必须通过converter包完成对象映射,并能结合仓库真实代码(如DbWebConverter、StorageConverter、CommandConverter)快速落地这一规范。
1. 目的:为什么对象转换必须集中化
Chat2DB 服务端是一个典型的分层多模块工程:chat2db-community-web(HTTP 层)、chat2db-community-domain-core(领域层)、chat2db-community-storage(存储层)、chat2db-community-spi(插件 SPI)、chat2db-community-plugins/*(数据库插件)与chat2db-community-tools(工具层)。对象在层与层之间传递时,字段名、类型、结构往往不一致,例如 HTTP DTO 的user字段到领域请求里叫username、type到dbType(见 DataSourceConverter 中的@Mapping)。
契约第一条强制约束:Controllers、Services、Adapters、Facades、存储实现、插件实现一律不得自行定义对象映射方法。理由有三:
- 避免字段映射散落各处——若每个类都写一段
new Xxx()+ setter,同一个字段的映射逻辑会重复出现在几十个文件里,改动一个字段名要全局搜索修改。 - 防止边界模型泄漏(boundary-model leakage)——服务层直接操作 Web DTO、存储层直接组装领域模型,会让模块边界形同虚设。
- 保证转换行为一致——集中到 Converter 后,null 处理、字段名转换、类型转换只实现一次,行为可预测、可测试。
需要说明的是,该契约仅约束Chat2DB 项目对象之间的结构化转换;JDK、Spring、JDBC 驱动、ANTLR、JSON 库等第三方类型的使用不在其管辖范围内。
2. 对象转换的定义:什么算、什么不算
契约给出明确判定标准:只要一个项目对象被转换为另一个项目对象,就属于对象转换,必须使用 Converter。
典型必须走 Converter 的场景:
- Web 请求/响应 DTO 与领域请求/响应之间的双向转换;
- 领域请求/响应与领域模型之间的转换;
- 领域模型与存储模型、实体或 record 之间的转换;
- Gateway、CLI、MCP、本地存储或插件结果对象与内部模型的转换;
- List、分页、树结构中元素级别的转换;
- 同一语义对象跨层传输时字段名/类型不同、或需要轻度规整(light normalization)的转换。
不属于强制 Converter 场景(无需硬套 Converter):
- 值解析,如
toString()、枚举的fromCode/fromValue/fromName; - 字符串、路径、SQL 片段、字节数组、原始 JDBC 值的格式化;
- 把异常适配为 HTTP 错误结果;
- 通用 JDK 集合工具方法,如
toMap、toList、toSet; - 测试夹具、Mock 与断言对象构造。
契约还特别提示:即使方法名不含convert,只要一个 helper 读取一个项目对象并返回另一个项目对象,就属于对象转换,不能靠命名规避规范。仓库中CommandConverter就是典型——它实现IDbSqlCommandService.toSqlExecuteRequest(...),方法名不带 convert,但内部正是通过param2model完成DbDlExecuteRequest→SqlExecuteRequest的映射(见 CommandConverter.java)。
3. Converter 归属:每个转换都有唯一的家
契约用一张归属表明确了“哪类转换放在哪个模块的 converter 包”,这是整个规范最核心的可执行部分,完整继承如下:
| 转换类型 | Converter 位置 | 说明 |
|---|---|---|
| HTTP DTO ↔ 领域契约对象 | chat2db-community-web的converter包 | 由 Controller、Web Facade、Adapter 调用 |
| 领域契约对象 ↔ 领域模型 | chat2db-community-domain-core的converter包 | 由 Service 实现调用 |
| 领域模型 ↔ 持久化对象 | chat2db-community-storage的converter包 | 由存储实现调用 |
| 插件内部模型 | 对应chat2db-community-plugins/*模块内的converter包 | 仅插件内部使用,不暴露给 web / domain-core |
| 共享 SPI 结果转换 | chat2db-community-spi中的converter包或显式 Converter 类型 | 只能依赖 SPI、domain-api、tools 允许的模型 |
| 工具层配置对象 | chat2db-community-tools中的显式*Converter | 只处理工具自有或第三方配置对象 |
新增业务对象转换时,统一放入converter包并命名为XxxConverter;既有的不在converter包中的*Converter类型也必须遵守纯映射规则。
仓库中每个模块都有对应的落地实例:
- web 层:DbWebConverter 位于
web/api/converter/db包,声明了request2param、dto2response、tableDto2response等几十个抽象映射方法,使用 MapStruct@Mapper(componentModel = "spring")在编译期生成实现。 - domain-core 层:DataSourceConverter、CommandConverter 等位于
domain/core/converter包。 - storage 层:StorageConverter 位于
storage/converter包,负责DataSource↔WorkspaceDataSource、DataSourceNamespace↔WorkspaceDataSourceNamespace、DbTablePinRequest→PinTable等映射。 - spi 层:DocumentConverter 位于
spi/converter包,负责把 MongoDB Document / Map 递归转换为LinkedHashMap(含Binary、Blob、byte[]的特化处理)。 - 插件层:MysqlRoutineConverter、RedisKeyConverter 等位于各插件模块内部。
- tools 层:ConsoleObjectConverter、NetworkProxySettingsConverter 等显式
*Converter类型。
4. 禁止的临时转换(Ad Hoc Conversion)
契约列出了七类绝对禁止在非 Converter 类中出现的写法,任何一条命中即视为违反规范:
- 禁止自定义映射方法名:非 Converter 类不得添加名为
toXxx、fromXxx、convertXxx或xxx2yyy的对象映射方法。 - 禁止手写 setter 拷贝:不得通过
new Xxx()后连续调用 setter 的方式从源对象拷贝字段。 - 禁止用 Builder 组装:不得通过 Builder 从另一个项目对象组装目标项目对象。
- 禁止反射拷贝:不得使用
BeanUtils.copyProperties、BeanUtil.copyProperties、PropertyUtils.copyProperties做项目对象转换。 - 禁止
ObjectMapper.convertValue做项目对象转换。 - 禁止 JSON 往返:不得写
JSON.parseObject(JSON.toJSONString(source), Target.class)这类序列化-反序列化来回。 - 禁止直接使用 MapStruct:非 Converter 类不得声明
@Mapper类型,也不得直接调用Mappers.getMapper。
契约给出的对照示例:
// 禁止:在 Controller 里写私有转换方法。 private ModelConfigSaveRequest toModelConfigParam(WebModelConfigSaveRequest request) { ModelConfigSaveRequest param = new ModelConfigSaveRequest(); param.setName(request.getName()); return param; } // 允许:Controller 委托 Converter 完成转换。 ModelConfigSaveRequest param = modelConfigWebConverter.request2param(request);结合仓库观察,@Mapper注解只出现在各模块converter包下的类中(web 层如 ChatConverter、DataSourceWebConverter、OperationLogConverter 等,domain-core 层如 SqlCompletionConverter),这一分布本身正是规范第 4、5 节的可验证证据。
5. 纯映射规则:Converter 只做结构映射
契约对 Converter 内部行为有严格约束,保证其“纯粹性”:
- 只允许字段映射、集合元素映射、null 处理、字段名转换、轻量类型转换。
- 可以使用MapStruct 的
@Mapping、@Mappings、@MappingTarget以及少量 default 方法。 - 可以包含转换所需的轻度规整,如枚举名转换、trim、展示脱敏、既定的加密/解密边界。
- 禁止注入或调用服务、存储组件、Mapper、Repository、客户端或
ApplicationContext。 - 禁止执行权限校验、状态流转、对象存在性检查、跨模块业务编排。
- 禁止吞异常返回默认对象,转换失败必须抛显式异常。
- 公共方法命名必须标明源和目标,例如
request2param、model2response、storage2model、toResponse。
关键判定:如果转换需要查库、远程调用或运行时上下文,它就不是纯对象转换。此时应由业务 Service 先拿到完整的源对象,再交给 Converter 做纯结构映射。
仓库中的方法命名完全符合该约定:DbWebConverter中的request2param(...)、tableDto2response(...)、schemaDto2response(...)、databaseDto2response(...)(见 DbWebConverter.java),StorageConverter中的dataSource2workspace/workspace2dataSource(见 StorageConverter.java)。
同时契约提示迁移期桥接模式:Converter 为实现某个转换接口而临时实现时,该接口只能作为桥,不得借接口调用业务能力,新代码不得扩大这种模式。仓库中的 CommandConverter 实现了IDbSqlCommandService接口,但toSqlExecuteRequest只是转发到param2model,正是这种受限桥接的典型示例。
6. 调用边界:谁可以调谁
契约按层规定了调用边界,防止绕过 Converter 组装他层内部模型:
- Controller:只负责 HTTP 绑定、校验触发与响应包装,不得手拼领域请求。
- Web Adapter:负责协议适配与调用编排,不得手拼 Gateway、领域或 Web 模型转换。
- Service 实现:负责业务编排与规则评估,不得手拼响应、模型或实体转换。
- 存储实现:负责持久化调用,不得手拼领域模型与存储对象的转换。
- 插件实现:负责提供插件能力,插件模型转换必须放插件本地 Converter。
- 跨模块:调用方只能依赖目标层的公共契约对象与本层所属的 Converter,不得绕过 Converter 组装另一层的内部模型。
这一点与仓库模块边界设计(见 spec/code/server/java-module-boundaries.md)相辅相成:Converter 的converter包位置决定了它只能访问所在层允许依赖的模型类型。例如 SPI 层的转换(DocumentConverter)不依赖任何 web / domain-core 的实现类,插件层的 MysqlRoutineConverter 只做插件内部模型转换、不外泄。
7. 允许的例外:不需 Converter 但必须保持窄范围
以下场景不需要 Converter,但必须保持范围狭窄,一旦开始从一个项目对象向另一个项目对象拷贝字段,就必须迁入 Converter:
- 静态工厂:对象上的静态工厂通过自身不变量约束创建,不拷贝他层字段。
- 枚举/值对象解析器:返回自身类型,如
fromCode、fromValue。 - SQL/JDBC 原始值处理器:返回
String、byte[]、原始值或裸驱动对象(例如 SPI 中 ResultSetConverter 这类面向 JDBC 原始值的工具)。 - 异常转换器:把异常适配为 HTTP 错误结果。
- 测试代码:构造测试数据。
- 启动装配/配置代码:创建 Bean 或配置属性对象,不映射业务对象。
8. 评审清单:如何检查对象转换是否合规
契约最后给出了可落地的 Code Review 清单,评审对象转换时必须逐项核对:
- 非 Converter 的生产文件不得使用 MapStruct
@Mapper或Mappers.getMapper。 - 非 Converter 文件不得用反射拷贝、JSON 往返或
ObjectMapper.convertValue做项目对象转换。 - 非 Converter 文件不得定义返回 Chat2DB 项目对象的
toXxx、fromXxx、convertXxx方法。 - Converter 文件不得依赖服务、存储组件、Mapper、Repository、实现类或 Spring Bean 查找。
*Converter与*Convertor类型必须位于converter/convertor包中,除非有明确的遗留例外。- 边界类不得出现可疑的
new + set映射序列。 - Converter 内部的反射拷贝、JSON 往返或
ObjectMapper.convertValue必须有显式理由。
同时契约也给出审慎条款:接口方法、SQL 补全/解析器候选对象构造、SQL Builder 临时对象不自动视为违规,需要结合上下文评审——这避免把规范误伤到合理的工具性代码上。
9. 规范在仓库中的验证与延伸
除了上述 Converter 实现外,读者还可以从以下文件继续深入验证本契约的执行情况:
- Web 层更多 Converter:/api/converter/ai/ChatConverter.java、/api/converter/er/ErWebConverter.java、/api/converter/redis/RedisKeyConverter.java、/api/converter/driver/JdbcDriverConverter.java。
- 插件层测试:
IGenericMetaDataConverterTest与MysqlSqlCompletionTokenCandidateConverterTest分别验证了 generic 插件与 mysql 插件的转换行为。 - 相关规范文档:java-module-boundaries.md(模块边界)、java-web-controller-contracts.md(Web 控制器契约)、java-interface-contracts.md(接口契约)。
实践建议:在 Chat2DB 仓库中新增一个跨层业务对象时,遵循三步走——先确定目标层与源层,按本文第 3 节归属表选择 Converter 所在模块;再在converter包中新建XxxConverter(或扩展既有 Converter)声明@Mapper(componentModel = "spring")抽象方法,使用@Mapping/@Mappings处理字段名差异与ignore;最后由业务代码委托调用,严格避免在 Controller、Service、存储实现中手写任何new + set或反射拷贝逻辑。配合第 8 节评审清单,即可保证对象转换集中、可测试且不泄漏边界模型。
【免费下载链接】Chat2DBChat2DB is a free, cross-platform, local-first database client and SQL workspace for developers, DBAs, analysts, and data teams. Connect to 40+ databases, manage data, edit and run SQL, and use your own AI model to generate, explain, and optimize queries. Available on desktop, web, Docker, and CLI, with MCP support.项目地址: https://gitcode.com/GitHub_Trending/ch/Chat2DB
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考