ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Yark代码生成器:从配置模板到一键生成完整CRUD服务

Yark代码生成器:从配置模板到一键生成完整CRUD服务 做后端开发这些年我写过太多重复代码实体类、Mapper、Service、Controller还有各种配置文件和 DTO。刚开始觉得没什么复制粘贴改改用不了几分钟可一旦项目多了、表结构改了、或者客户要求换个字段风格那滋味是真难受。所以当我第一次接触 Yark发现它不只是一个简单的模板填充工具而是一套能根据结构化描述自动生成完整工程骨架的代码生成工具时我第一反应是这玩意儿要是早点出现我起码能少掉两斤头发。Yark 的核心思路很朴素你写一份描述“项目长什么样”的配置文件再配上一套定义“代码怎么长”的模板剩下的脏活累活由它来完成。它不绑定任何特定语言或框架我拿它生成过 Java 的 Spring Boot 项目也生成了几套 Python 的 FastAPI 服务最近甚至用它给前端同事批量生成过 TypeScript 的 API 调用模块。这篇文章我不打算给你念官方文档而是从一个实际使用的角度把这套工具的设计逻辑、实操流程、还有我踩过的坑一次讲清楚。1. 项目背景为什么还需要一个代码生成工具1.1 Yark 解决的痛点先说说没有代码生成工具时我的工作流是什么样。拿到一张新表或者接到一个“做一个用户管理模块”的需求我会先建数据库表然后打开 IDE开始写实体类。一个字段一个字段地敲varchar 映射成 Stringbigint 映射成 Longdatetime 映射成 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 compilePython 项目跑 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、没有不安全的日志输出。模板虽然是代码生成器但里面写什么内容的决定权还是在你手里安全风格、日志规范这种东西应该从一开始就固化在模板里而不是靠生成后再手工改。
返回列表