Yark代码生成器:从配置模板到一键生成完整CRUD服务
2026/9/24 23:21:48 网站建设 项目流程

做后端开发这些年,我写过太多重复代码:实体类、Mapper、Service、Controller,还有各种配置文件和 DTO。刚开始觉得没什么,复制粘贴改改用不了几分钟,可一旦项目多了、表结构改了、或者客户要求换个字段风格,那滋味是真难受。所以当我第一次接触 Yark,发现它不只是一个简单的模板填充工具,而是一套能根据结构化描述自动生成完整工程骨架的代码生成工具时,我第一反应是:这玩意儿要是早点出现,我起码能少掉两斤头发。

Yark 的核心思路很朴素:你写一份描述“项目长什么样”的配置文件,再配上一套定义“代码怎么长”的模板,剩下的脏活累活由它来完成。它不绑定任何特定语言或框架,我拿它生成过 Java 的 Spring Boot 项目,也生成了几套 Python 的 FastAPI 服务,最近甚至用它给前端同事批量生成过 TypeScript 的 API 调用模块。这篇文章我不打算给你念官方文档,而是从一个实际使用的角度,把这套工具的设计逻辑、实操流程、还有我踩过的坑一次讲清楚。

1. 项目背景:为什么还需要一个代码生成工具

1.1 Yark 解决的痛点

先说说没有代码生成工具时,我的工作流是什么样。

拿到一张新表,或者接到一个“做一个用户管理模块”的需求,我会先建数据库表,然后打开 IDE,开始写实体类。一个字段一个字段地敲,varchar 映射成 String,bigint 映射成 Long,datetime 映射成 LocalDateTime。写完实体类写 Mapper,写完 Mapper 写 Service 接口,写完接口写实现类,最后是 Controller。这套流程熟练到闭着眼都能写,但问题也在这里:太机械了,机械到任何一个标点符号都不需要动脑子。

这还只是单个模块的情况。如果项目里有几十张表,如果客户临时要在所有实体类里加一个 createBy 字段,如果是多人协作时大家对命名规范的理解还不一致,那改动量会成倍放大。Yark 想解决的,正是这种“高重复、低创造性、却容错率很低”的编码场景。它不会替你思考业务逻辑,但能把那些占掉你大量精力的样板代码一口气全部生成出来。

1.2 Yark 与主流方案的对比

你可能听说过或者用过类似的工具,比如国外老牌的代码生成器,或者一些大厂开源出来的脚手架工具。我自己也试过几个,选择 Yark 而不选其他方案,原因主要有三点。

第一,Yark 是配置驱动、模板驱动的双引擎设计,而不是写死在代码里的生成逻辑。有些工具内置了固定的代码风格,生成出来的代码一看就是那个工具的“味道”,改起来比重写还费劲。Yark 的模板是你自己定义的,生成出来的代码就是你平时手写的样子,完全看不出来是机器生成的。

第二,Yark 对输入格式的要求非常低,支持 JSON 和 YAML 两种描述格式,也支持简单的中文键值映射。这意味着即使团队里没有专人维护一个复杂的元数据模型,普通开发也能在十分钟内写出一份可用的项目描述文件。

第三,Yark 的输出是模块化的,不是一锅端。你可以只生成实体层,也可以只生成 Controller 层,也可以指定某几个模板单独运行。这一点在项目迭代阶段特别有用,改了数据库字段之后,我只想重新生成实体类,不想把整个项目再覆盖一遍。

我不打算把 Yark 吹成“银弹”,它也不是万能的。但如果你受够了纯手工堆砌样板代码,又不想为了一个简单功能引入一套重量级框架,Yark 属于那种轻量、灵活、能让你真正掌控生成结果的工具。

2. 核心设计思路与关键特性

2.1 配置驱动与模板执行分离

Yark 最让我喜欢的一点,是它把“项目描述”和“生成逻辑”彻底拆开了。

项目描述文件(通常叫 yark.json 或 yark.yaml)里只写“有什么”,不写“怎么生成”。比如一个用户模块,我只需要声明模块名叫 user,包含哪些字段,每个字段的类型、是否必填、长度限制等信息。至于这个字段在 Java 里映射成 Long 还是 Long,在 Python 里映射成 int 还是 Optional[int],那是模板要关心的事情。

模板是另一套独立的文件,里面是类似 FreeMarker 或 Jinja2 的占位符语法。Yark 在读取项目描述后,会把一系列变量注入到模板上下文中,包括全局信息、模块信息、字段列表、生成时间、用户配置的自定义参数等。模板只需专注一件事:接收到什么变量,就渲染出什么文本。

这种设计的好处在实际使用中非常明显。换数据库、换语言、换代码风格,我只需要改模板而不用动项目描述;反过来,新加一个模块,我也只需要改项目描述而不用动任何模板。两边的改动被完全隔离,出问题的概率自然就小得多。

2.2 模板语言的设计逻辑

第一次看 Yark 的模板文件,你可能会觉得它和普通的文本模板没什么两样。确实,它在语法上做了很多借鉴,基本概念就是变量插值、条件判断、循环迭代、宏定义,外加一些针对代码生成场景的内置函数。

以我常用的一个实体类模板为例,核心逻辑是这样的:

package {{ package_name }}.entity; import java.time.LocalDateTime; import lombok.Data; @Data public class {{ entity_name }} { {% for field in fields %} /** * {{ field.description }} */ private {{ field.java_type }} {{ field.field_name }}; {% endfor %} }

这里有几个关键设计值得展开说。

循环是模板里最基本也最常用的结构。一个模块往往有多个字段,每个字段都要渲染成一行成员变量,用 for 循环可以很自然地处理。而且 Yark 在循环过程中会额外注入一个 loop 对象,提供当前索引、是否是第一个元素、是否是最后一个元素、字段总数等信息。生成 MyBatis 的 resultMap 或者生成批量插入语句时,这些信息非常有用,比如判断是不是最后一个字段来决定要不要加逗号。

条件判断则可以用来处理“可空字段”这种特殊场景。Java 里一个可空的 String 通常直接写成 String,但在 Kotlin 里就要写成 String?,在 TypeScript 里则是 string | undefined。我可以在模板里这样写:

{% if field.nullable %} private {{ field.java_type }}? {{ field.field_name }}; {% else %} private {{ field.java_type }} {{ field.field_name }}; {% endif %}

宏定义是我用得最多的功能。把一段固定的渲染逻辑抽到一个公共宏里,多个模板共同引用,避免重复。这和编程语言里的函数是同一个道理。比如生成查询条件时,不同表之间只有字段名不同,逻辑完全一样,那我就在公共宏里定义一次条件渲染逻辑,然后在各个查询模板中调用,既省事又统一。

2.3 多模块协作机制

实际的项目很少只有一个孤零零的模块,Yark 在设计上对多模块场景做了比较完整的支持。

在项目描述文件里,可以配置多个 module,每个模块可以拥有自己的字段集合、存储表名、接口前缀等属性。生成时既可以一次性处理全部模块,也可以按模块名精确处理其中一个。比如我现在维护的后端服务,订单和用户两个模块就在同一个项目描述文件里,但我对订单改了接口,只需单独跑一下订单模块的 Controller 模板,用户模块完全不挨碰。

模块之间还能声明依赖关系。Yark 在生成代码时会把这种依赖关系也暴露给模板。例如订单模块引用了用户模块的实体,模板里就可以根据订单模块的依赖声明,自动生成 import 语句,不需要我手工写。这种跨模块的信息联动,是我在一开始没预料到的惊喜,使用越久越觉得顺手。

3. 实操:从空目录到生成可编译的 CRUD 服务

3.1 环境准备与初始化

Yark 的安装没有太多花哨的步骤,官方提供了一个命令行工具,下载对应平台的二进制包,解压后把可执行文件放到 PATH 里即可。我平时在 Mac 和 Linux 服务器上都部署过,没有依赖特殊的运行时环境,这一点对团队推广非常友好。

安装完成后,先建一个工作目录,跑一下初始化命令,Yark 会在当前目录生成一份推荐的目录骨架和一个默认的配置文件模板。我的习惯是所有模板文件统一放到 templates 目录下,生成产物放到 output 目录下,项目描述文件放在根目录。这样的结构即使隔了几个月回来也能一眼看明白。

mkdir my-yark-demo && cd my-yark-demo yark init yark list-templates

第一条命令完成初始化,第二条命令可以查看当前模板包里有哪些现成的模板。默认模板包覆盖了 Java Spring Boot、Python FastAPI、Go Gin 三套主流方案,对我这种经常切换语言的人来说非常实用。

3.2 编写项目描述文件

这一节是整个使用过程中最需要动脑子的部分。项目描述文件的质量,直接决定最终生成代码的质量。我先拿一个简单的用户管理模块做示例。

project: name: user-service packageName: com.example.user javaVersion: 17 database: type: mysql tablePrefix: t_ modules: - name: user tableName: t_user apiPrefix: /api/user fields: - name: id type: bigint primaryKey: true autoIncrement: true description: 主键ID - name: username type: varchar length: 64 nullable: false description: 用户名 - name: email type: varchar length: 128 nullable: true description: 邮箱 - name: createdAt type: datetime nullable: false description: 创建时间

这份描述文件的核心是 modules 下的字段列表。每一项字段声明包含 name、type、length、nullable、description 等属性,Yark 会把这些原始信息解析后以不同形态提供给模板。比如某个字段在数据库里是 varchar(64),模板里可以根据需要渲染成 Java 的 String、Python 的 str、JSON Schema 里的 string 加 maxLength。

写描述文件时我的建议是字段的 description 一定要写,最好不要偷懒。这个描述不仅会出现在生成的注释里,而且在一些场景下 Yark 会自动为它分词并生成方法注释。我见过团队里有人图省事不写 description,生成的代码看起来干巴巴的,后期维护时完全看不出这段代码当初是干什么用的。

3.3 编写模板文件与生成产物

项目描述文件准备好之后,接下来就是模板的编写。如果你不想完全从零开始,可以先从默认模板里复制一份出来做修改,熟悉语法之后再根据自己的习惯去调整。

以生成一个 Spring Boot 的实体类为例,模板文件放在 templates/entity.java.jinja,内容大致如下:

{% macro entity_comment(entity) -%} /** * {{ entity.description }} */ {%- endmacro %} package {{ project.packageName }}.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @TableName("{{ module.tableName }}") {{ entity_comment(module) }} public class {{ module.name | pascal }} { {% for field in module.fields %} {% if field.primaryKey %} @TableId(type = IdType.AUTO) {% endif %} {% if field.description %} /** {{ field.description }} */ {% endif %} private {{ field | java_type }} {{ field | camel }}; {% endfor %} }

这段模板里用了几个 Yark 内置过滤器,pascal 可以把下划线命名转成帕斯卡命名,camel 可以转成驼峰命名,java_type 则是根据字段类型和长度自动推断对应的 Java 类型。这些内置过滤器节省了大量手写判断的活,是 Yark 模板和普通文本模板拉开差距的地方。

执行生成命令:

yark generate --config yark.yaml --module user --templates entity.java.jinja

执行完成后,output 目录下就会出现一个 Java 文件。打开看一眼,生成的代码完整可编译,跟我平时手写风格非常接近,基本不需要再调整。如果需要生成整套 CRUD,也可以不指定 templates 参数,Yark 会按默认配置文件里的顺序依次渲染所有模板,一次性生成 Controller、Service、Mapper、XML 等文件。

3.4 生成完毕后的检查与调试

代码生成出来不代表直接就能用,我还是建议做一个快速验证,而不是盲目信任生成结果。我的标准检查流程分三步。

第一步,看结构是否完整。生成的目录结构是否符合项目的包名路径,是否缺少某个关键文件。第二步,编译验证。Java 项目跑 mvn compile,Python 项目跑 python -m compileall,没有报错才算基础过关。第三步,抽查几个关键文件的逻辑。重点看关联关系和类型映射是否符合预期,尤其是日期类型和枚举类型这两类容易出问题的地方。

如果发现问题,优先回到模板或描述文件修改,而不是直接改生成好的代码。因为生成代码是会被覆盖的,你这次手工改了,下次重新生成又变回原样,那等于问题没解决。

4. 常见运行问题与排查实录

4.1 模板变量未渲染或渲染错乱

症状是生成出来的文件里还有大段的占位符,或者变量名周围多了奇怪的空白字符。通常原因有三种:变量名拼写错误、变量在模板上下文中不存在、循环或条件语句的标记没有闭合。

排查方法最直接的就是在 Yark 的命令行里加上 debug 参数,让它把当前模板可用的全部变量打印出来,对照着看你的引用路径是不是写错了。我在第一次写跨模块引用时,把 module 写成了 modules,结果死活取不到值,打开 debug 才发现的。

另外提醒一点,Yark 的模板语法里 if 和 endif、for 和 endfor 必须严格匹配,用缩进方式表达层级在 Yark 里是无效的,必须写完整的结束标签。这个和 Python 的语法习惯不太一样,Python 背景的同事第一次用很容易在这里栽沟里。

4.2 输出文件覆盖与备份

代码生成工具最让人纠结的一个问题是“生成会不会覆盖我改过的东西”。Yark 默认采用的策略是:目标文件已存在且内容没有变化时跳过,有变化时会生成一个新文件并加上时间戳后缀,避免直接冲掉原有文件。

但这个策略只说对了一半,如果你在模板里定义了 output 路径为固定文件名,且目标文件存在的话,Yark 会先询问是否覆盖。我不建议在脚本里无条件选择强制覆盖,我的做法是在生成前先把 output 目录打包备份一次,再把生成结果和上一版做差异对比,确认没问题后再决定是否替换。

cp -r output output_backup_$(date +%Y%m%d_%H%M%S) yark generate --force git diff --no-index output_backup_latest output

用 git diff 对比生成前后的差异,能很直观地看到这次改动影响到了哪些文件、哪些行,做到心中有数。

4.3 多模块生成顺序依赖

当多个模块之间互相引用时,生成顺序就很重要了。比如 A 模块的某个文件需要 import B 模块的实体类,如果 B 模块还没生成,A 模块的生成虽然不会失败,但生成结果中会缺少关键 import。

Yark 提供了一套模板依赖声明机制,在每个模板文件的头部用注释声明它依赖哪些其他模板,Yark 会基于这些依赖自动构建执行顺序图,并按拓扑排序依次渲染。我第一次用的时候没有设置这个,结果生成的订单模块没法编译,排查了半天才发现是缺少用户模块的 import。后来给模板加上依赖声明后,这个问题就再也没出现过。

这里我建议把逻辑上独立的低层模块,比如系统基础实体、公共工具类,设置为最先执行;高层业务模块比如订单、支付,设置为后执行。依赖声明不仅是个顺序问题,也是一种自文档化的方式,后来接手模板的人一看就知道谁依赖谁。

5. 进阶玩法与实际工程落地

5.1 用一套模板维护多个技术栈

我目前维护的一个项目同时存在两个技术栈:老系统是 Java 的,新系统是 Go 的。按照以前的模式,数据库表结构一变,两边都要手动同步,特别容易漏改。而我在 Yark 里写了两套模板,分别对应 Java 和 Go 的输出格式,用的是同一个项目描述文件。

数据库里加一个字段,我只需要在 yark.yaml 里加一行字段声明,然后跑两条命令分别生成 Java 和 Go 代码。因为模板逻辑是完全独立的,两边生成的代码风格也是各自团队约定俗成的样子,不会出现“Java 代码带着 Go 的命名习惯”这种尴尬。这个场景下,Yark 的价值不仅仅是省时间,更在于把“多端一致性”从一个口头约定变成了一件自动完成的事。

5.2 与 CI 流程的配合

Yark 提供了纯命令行的调用方式,这意味着可以很方便地接入 CI 流水线。我在团队里搭过一个流程:每次数据库表结构变更后,提交一个 Dockerfile 让 CI 自动拉取最新的表结构信息,转换成 Yark 的字段描述,然后执行生成命令,把生成的代码提交到一个独立的 MR 里供开发审阅。这样既保证了生成代码和表结构同步,又不会在代码评审之前就直接覆盖开发本地的大量修改。

执行上无非就是在 CI 的某个 step 里调用 yark generate,和普通命令没什么区别。但有几个细节需要注意:CI 环境里没有交互式终端,要预先配置好非交互模式;生成目录的写权限要提前确认;模板文件和描述文件要随代码一起走版本管理,方便追溯某次生成的代码对应的是哪个版本的模板和描述。

5.3 什么时候不该用 Yark

作为忠实用户,我必须诚实地提醒一句:代码生成工具不是万能的,Yark 也确实有它的适用范围。

业务逻辑层,尤其是那种充满复杂判断、状态流转、外部依赖调用的业务逻辑,不适合用代码生成工具来做。生成器擅长的是结构固定、可枚举、按规则排列的内容,而真正复杂的业务逻辑恰恰是反例。我见过有人试图把订单状态机的流转逻辑也用模板生成,最后模板文件里写了一堆复杂的条件判断,比手写对应的业务代码还要难维护,这就完全背离了工具初衷。

另外,如果你的项目还处在探索阶段,需求每天都在变,稳定下来之前不必急着上模板。因为需求和字段变化越剧烈,你花在维护项目描述文件上的时间就越多,最后可能不但没省时间,反而增加了额外负担。

我个人的判断标准很简单:同样的代码,如果在项目里出现了三次以上而且形式很接近,我就会考虑把它抽象到模板里。如果只是偶尔出现一次两次,老老实实手写反而更快更稳。Yark 的价值在于让你把时间花在更有创造性的地方,而不是制造另一种更复杂的重复劳动。

5.4 我沉淀下来的几个使用习惯

最后分享几个我用了很久之后沉淀下来的小习惯,希望对你有帮助。

第一个习惯是模板文件里写版本注释。我在每个模板头部固定保留一行业TODO注释,标明这个模板最后修改的日期和原因。模板是经常要改的东西,没有版本信息的话,很难追溯某段逻辑是什么时候因为什么原因加进来的。

第二个习惯是项目描述文件里所有字段都要带缩进规范。YAML 对缩进要求严格,我一开始吃过几次亏,后来强制自己统一用两个空格做缩进,不用 Tab,也要求提交代码前先跑一遍 yark validate 命令验证格式。这条命令能提前拦截七成以上的配置错误。

第三个习惯是每次生成完成后跑一次全量编译。无论只是改了一个模板还是新增了一个模块,我都会执行一次完整的项目编译,而不是只看生成过程有没有报错。模板渲染阶段正常不代表生成出来的代码在语言层面就是合法的,全量编译是最后一道防线。

第四个习惯是给每个模块的生成结果做一次 keyword 扫描。比如确认生成的代码里没有 TODO 残留、没有写死的测试 IP、没有不安全的日志输出。模板虽然是代码生成器,但里面写什么内容的决定权还是在你手里,安全风格、日志规范这种东西应该从一开始就固化在模板里,而不是靠生成后再手工改。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询