DTO、JSON 与 Message Bean

本页回答三个高频问题:DTO 默认怎么写,什么时候跟随 message bean 风格,以及 JSON / YAML 应该走什么入口。

默认选择

场景 默认做法
当前任务里的局部 request / result DTO @DataBean + getter / setter;需要跨边界序列化时再补 Serializable
模块里已有成体系的 API message bean @DataBean + ExtensibleBean + @PropMeta
很小的不可变值对象 仅在 surrounding style 已存在时使用构造函数 + @JsonProperty
JSON / YAML 解析与序列化 JsonTool

DTO 默认规则

局部 DTO 的默认规则仍然很简单:

  1. @DataBean
  2. 如果这是普通局部 Bean,默认提供标准 getter / setter;只有需要跨边界序列化或周边代码已经这样做时,再补 Serializable
  3. 默认放在最贴近复用边界的位置(详见下方"DTO 放置规则")。
  4. 不要默认用 Map<String, Object> 代替强类型 DTO。

如果只是当前任务中的局部请求和返回对象,通常不需要上升到 message bean 模式。

DTO 放置规则

实体能表达的数据优先用实体(字段可见性由 xmeta 控制)。但汇总统计、简化视图、组合数据等场景使用 DTO 是正常的。

DTO 类型 放在哪里 例子
BizModel / Processor 共享的局部 DTO *-dao/.../dto/ SubmitOrderRequest
只在一个 BizModel 方法中使用的临时 DTO *-service/ 内靠近 BizModel 简单 result bean
外部 RPC 接口的 Message Bean(跨模块/跨端) *-api/.../beans/ WfStartRequestBeanChatRequest

关于 *-api/ 的常见误解:

  1. *-api/ 只放外部系统通过 HTTP/RPC 调用本模块时使用的 Message Bean。这些通常由 codegen 生成,配合 typed service interface(如 WorkflowService)使用。
  2. BizModel 方法内部使用的局部 DTO 不属于 *-api/ 即使 BizModel 方法通过 GraphQL 暴露为 API,其返回值使用的 @DataBean 仍然是模块内部实现细节,放在 *-dao/.../dto/*-service/
  3. I*Biz 接口放在 *-dao/.../biz/,不在 *-api/
  4. 判断标准:如果这个 DTO 的消费者是"同一模块内的 BizModel / Processor",放 *-dao/*-service/;如果消费者是"外部系统或其他模块通过 RPC 调用",放 *-api/

什么时候跟随 Message Bean 风格

仓库里已经存在一套稳定 message bean 模式,例如 LoginRequestLoginResult

  1. @DataBean
  2. extends ExtensibleBean
  3. getter 上使用 @PropMeta(propId = N)
  4. 在 getter 上按需加 @JsonInclude@JsonIgnore

这种风格适合:

  1. 模块内已经大量使用同一模式。
  2. 这是跨模块、跨端或平台级 API message。
  3. 需要可扩展属性和稳定的属性序号。

不适合:

  1. 只在一个 BizModel 方法里临时使用的本地 DTO。
  2. 为了“更高级”而把简单 DTO 复杂化。

@PropMeta 与 Jackson 注解怎么用

注解 默认用途
@PropMeta 为 message bean 属性分配稳定序号
@JsonInclude 控制可选字段何时输出
@JsonIgnore 排除内部或计算属性
@JsonProperty 构造函数参数名或特殊 JSON 字段名

默认跟随仓库已有风格:优先把这些注解放在 getter 上,而不是自己发明另一套写法。

JSON / YAML 默认入口

不要默认散用第三方 JSON 库。当前仓库的统一入口是 JsonTool

场景 默认方法
已知目标类型的 JSON 文本 JsonTool.parseBeanFromText(text, Target.class)
已知目标类型的 YAML 文本 JsonTool.parseBeanFromYaml(text, Target.class)
动态 JSON 对象 JsonTool.parseMap(text)
紧凑输出 JSON JsonTool.stringify(obj)
格式化输出 JSON JsonTool.serialize(obj, true)
输出 YAML JsonTool.serializeToYaml(obj)
从资源路径加载 JSON / YAML JsonTool.loadBean(path, Target.class)
从 Delta 资源层合并后加载 JsonTool.loadDeltaBeanFromResource(resource, Target.class)

最常见的默认写法:

LoginRequest request = JsonTool.parseBeanFromText(text, LoginRequest.class);
Map<String, Object> raw = JsonTool.parseMap(text);

String payload = JsonTool.stringify(result);
String pretty = JsonTool.serialize(result, true);

如果手里只有虚拟路径,先取 IResource,再调用 JsonTool.loadDeltaBeanFromResource(...)

实用判断

问题 默认回答
这个 Request DTO 要不要加 @DataBean
要不要一律实现 Serializable 不要一刀切,按周边风格和跨边界需求判断
要不要默认继承 ExtensibleBean 不要,除非周边 API 已经明确使用 message bean 模式
JSON 解析用什么 JsonTool
YAML 解析用什么 JsonTool.parseBeanFromYamlloadBean
可选字段怎么控制序列化 @JsonInclude
内部字段怎么排除 @JsonIgnore

常见坑

  1. 在简单局部 DTO 上无意义地引入 ExtensibleBean
  2. 新写一套与周边模块不一致的 @PropMeta 编号风格。
  3. 已知类型的数据仍然先 parseMap 再手动转 Bean。
  4. 在普通开发里默认引入另一套 JSON 工具,而不是先看 JsonTool

相关文档

  • ./service-layer.md
  • ../03-runbooks/create-request-response-dto.md
  • ../04-reference/common-java-helpers.md
  • ../04-reference/source-anchors.md