跳到主要内容

ADR-0030:DDL 模板引擎(Freemarker 终态 + doT 遗留桥)

  • 状态:已实现(2026-08-09,Freemarker 终态切片完成)
  • 前置:ADR-0022 工作区↔版本↔实库;docs/data-format.md projectJSON database[] 模板字段

背景​

  • projectJSON 内 DDL 模板沿用 doT.js 语法(defaultData.json);历史前端 json2code.ts 曾用于预览/导出。
  • 版本面板 /hisProject/diff 的 ddl 必须由后端权威生成;前端只渲染 API(ADR-0022 延伸)。
  • 约束:JVM 原生高性能 / 低内存;禁止 Nashorn/GraalJS/ScriptEngine 嵌入 doT.js;不长期维持双引擎。
  • 调研结论:Handlebars 双端同文在 FE 不再执行 DDL 后失去同构收益,JVM 吞吐约为 Freemarker 的 ~1/5,故不作为终态。

决策(终态)​

1. 单一权威:后端生成 DDL​

  • 版本详情 / 对比、导出、同步 SQL 等 product path 的 DDL 一律由后端生成。
  • 前端 禁止 在产品路径调用 generateUpdateSql / json2code 重算 DDL;只渲染 API 返回的 ddl 字符串。
  • FE 版本面板、顶栏 dirty、导出与实库同步 SQL 随切片迁移至后端 API;doT 前端运行时逐步退役(非 product path 调试工具可暂留)。

2. 引擎:Freemarker(终态);Pebble(过渡)​

阶段运行时说明
过渡(2026-08-09)Pebble 3.x已落地:DdlPebbleTemplateEngine + 编译缓存;满足「无脚本引擎、fail-closed」
终态(2026-08-09)FreemarkerDdlFreemarkerTemplateEngine 已替换 Pebble;classpath 种子 ddl/freemarker/{dialect}/*.ftl;Pebble 依赖已移除
  • Pebble 过渡实现 已完成替换;本 ADR 终态已落地。
  • 迁移完成:DdlFreemarkerTemplateEngine 替换 DdlPebbleTemplateEngine;classpath 种子迁至 ddl/freemarker/{dialect}/*.ftl;单测/golden 对齐后移除 Pebble 依赖。

3. 模板与遗留兼容​

  • 官方/种子模板:语法统一为 Freemarker;defaultData.json 与 classpath 默认模板随 Freemarker 切片迁移。classpath 种子目录 ddl/freemarker/{mysql,postgresql,oracle}/*.ftl 与 MySQL 键集对齐(11 项);defaultData 无 SQL Server 方言,故暂无 sqlserver/ 种子。
  • 解析优先级:DdlTemplateRenderer.resolveFtlSource — projectJSON database[] 自定义模板 优先;仅当字段为空时回落 classpath 种子。
  • 用户存量 doT(projectJSON database[] 自定义模板):读时经 DotToFreemarkerTranslator + DdlTemplateContextEnricher 预计算 pkFieldNames、sameCols 等 evaluate,非永久双引擎——翻译层仅为 legacy bridge。
  • 写入新模板:projectJSON 增加 templateSyntax: freemarker | dot(dot 仅兼容旧稿;新稿默认 freemarker)。未标注且含 doT 特征时按 dot 处理。
  • 编排不变:Json2CodeDdlEngine.generateUpdateSql → VersionDdlEngine / VersionPanelDiffEngine。

4. 明确拒绝(终态)​

方案拒绝理由
Handlebars.js + Handlebars.java 双运行时FE 不再执行 DDL,同构税不值;JVM 侧约 5× 慢于 Freemarker
Mustache逻辑过弱,无法表达现有 doT 循环/条件/helpers
Liqp非 JVM 原生主路径;维护与 classpath 负担
GraalJS / Nashorn / ScriptEngine + doT.js与用户约束冲突;内存与沙箱风险
永久 Pebble + doT 双引擎与「单一 JVM 终态引擎」冲突;Pebble 仅过渡

5. 明确不做​

  • JVM 内嵌 doT.js;无测试的手写 SQL 拼接兜底;为「前端预览」保留与后端并行的 product DDL 路径。

调研备忘(归档)​

方案同文件 FE+BEJVM 吞吐覆盖现有 doT结论
Freemarker❌(BE-only 后不需要)✅ 最优✅ FTL + enricher终态
Pebble + Translator❌✅ 良好✅ defaultData 子集过渡
Handlebars.js + Handlebars.java✅⚠️ ~5× 慢✅拒绝终态
Spring Script + doT.js✅❌✅拒绝
Mustache❌✅❌拒绝

后果​

  • ✅ 版本 DDL 与 changes 同源;fail-closed(DdlTemplateException)。
  • ✅ 存量 projectJSON doT 无需用户立即改写;翻译桥直至用户主动迁移 FTL。
  • ✅ 终态单一 JVM 引擎(Freemarker),无 perpetual dual-engine。
  • ⚠️ FE export(getAllDataSQLByFilter)仍走前端 doT——非版本模块 product path,按 roadmap 切片关闭 ✅(2026-08-09:POST /hisProject/exportDdl + POST /hisProject/tableDdl;HTTP 分域见 ADR-0031 → /projectDdl/*)

验证​

过渡(Pebble,当前)

  • DdlPebbleCompatibilityTest:defaultData MYSQL createTableTemplate → CREATE + 字段 + PK
  • VersionDdlEngineTest:N entity add → N CREATE TABLE + FK

终态(Freemarker,已实现)

  • DdlFreemarkerCompatibilityTest:defaultData MYSQL createTableTemplate → CREATE + 字段 + PK
  • VersionDdlEngineTest:N entity add → N CREATE TABLE + FK
  • DotToFreemarkerTranslator 对存量 doT fixture
  • classpath ddl/freemarker/** 作方言兜底
  • FE 版本模块 product path 无 generateUpdateSql / getAllDataSQL(sync 改调 /hisProject/syncSql)
  • FE 导出 / 表元数据 DDL 预览无本地 json2code(改调 /hisProject/exportDdl、/hisProject/tableDdl)

下一步实现切片​

  1. DdlFreemarkerTemplateEngine + ddl/freemarker/{dialect}/*.ftl 种子 ✅
  2. DotToFreemarkerTranslator ✅
  3. 单测/golden 对齐后删除 Pebble DDL 专用代码与 pebble 依赖 ✅
  4. FE:版本 sync SQL 改调 /hisProject/syncSql ✅;export 仍 FE doT(非版本模块) ✅(/hisProject/exportDdl + /hisProject/tableDdl)
  5. templateSyntax 字段写入与 data-format.md 文档 ✅