JHipster RFC-5 解读:在生成器中用包含式语法(Inclusive Syntax)管理属性
2026/9/20 17:16:48 网站建设 项目流程

JHipster RFC-5 解读:在生成器中用包含式语法(Inclusive Syntax)管理属性

【免费下载链接】generator-jhipsterJHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.项目地址: https://gitcode.com/gh_mirrors/ge/generator-jhipster

导读

JHipster 的生成器内部同时存在排他式(exclusive)与包含式(inclusive)两种选项风格:前者如skipClientskipServer,后者如dto Aservice A with serviceImpl。混用两种风格会导致生成器内大量重复的业务逻辑与认知负担。RFC-5(JHipster-RFC-5)定义了生成器内部选项数据模型的标准结构:所有选项一律采用包含式语法——未定义时取合理默认值,已定义时遵循用户决策。本文完整翻译并深度解读该 RFC,同时结合本仓库源码(lib/jhipster/application-options.tslib/jdl/core/built-in-options/unary-options.tsgenerators/base-application/internal/utils.ts等)印证其设计动机与落地路径,帮助读者理解 JHipster 生成器配置模型的演进方向。

文档定位与基本信息

RFC-5 是 JHipster 技术决策文档(RFC 系列)中的一份设计提案,核心元信息如下:

  • Feature Name:Inclusive syntax to manage properties in generator
  • Start Date:V8(即面向 JHipster V8 版本规划)
  • 关联 Issue:jhipster/generator-jhipster#14416

该 RFC 的目标是定义生成器内部选项数据模型的标准结构。需要特别说明的是,该标准并不强制要求 JDL 或 CLI 层立即全面改造——文档原文明确指出"not necessary the jdl or cli, which should only be encouraged for new ones",即 JDL 与 CLI 只需对新增选项鼓励采用新语法,存量选项可以渐进迁移。

动机(Motivation):排他语法与包含语法混用的代价

当前 JHipster 的选项风格不统一:

  • 部分选项使用排他语法(exclusive syntax),例如skipClient(跳过客户端);
  • 部分选项使用包含语法(inclusive syntax),例如dto A(为实体 A 生成 DTO)。

两种风格混用会带来三个层面的问题:

  1. 额外的业务逻辑:生成器必须同时处理"跳过某物"和"启用某物"两种语义,分支判断大量重复;
  2. 认知负担:代码中容易出现双重否定,例如if (!skipClient) {},阅读时难以一眼理解逻辑;
  3. 框架能力受限:排他语法天然不利于表达"按需启用能力"这类正向诉求,限制了生成器可扩展的方向。

从本仓库源码可以印证上述成本。在 lib/jhipster/application-options.ts 中,排他选项被集中登记:

SKIP_CLIENT: 'skipClient', SKIP_SERVER: 'skipServer', SKIP_USER_MANAGEMENT: 'skipUserManagement',

而 lib/jdl/core/built-in-options/unary-options.ts 将skipClientskipServernoFluentMethodreadOnlyfilterembedded一起定义为一元选项(unary options)。一元选项的语义是"选项名出现即生效",这种"出现即否定/即启用"的设计正是排他语法模糊性的来源之一。

此外,在 lib/jdl/core/built-in-options/tokens/application-tokens.ts 中可以看到,skipClientskipServer为了同时兼容"应用配置项"与"实体选项"两种场景,被迫同时被归类为KEYWORDUNARY_OPTION

// This is actually needed as the skipClient & skipServer options are both entity & app options... if (['SKIP_CLIENT', 'SKIP_SERVER'].includes(tokenConfig.name)) { tokenConfig.categories.push(KEYWORD, UNARY_OPTION); }

这种"一个选项承担多重身份"的权宜之计,正是 RFC-5 试图根治的复杂度。

指南级说明(Guide-level explanation):两条核心规则

RFC-5 提出的方案非常简洁,选项应当始终是包含式的,并遵循两条规则:

  1. 如果最终用户没有定义该选项:生成器采用一个合理的默认值(reasonable default);
  2. 如果最终用户指定了该选项:生成器尊重最终用户的决策。

规则示例:以skipClient迁移为generateClient为例

RFC 原文用一个具体例子说明如何将排他的skipClient迁移为符合规范的包含式选项:

  • 一个没有声明任何generateClient属性的 JDLapplication,将正常生成该应用的前端(采用合理默认值);
  • 一个声明了generateClient属性的 JDLapplication,则遵循用户选择——若设置了generateClient: false,则不生成前端。

这一语义与当前仓库中"默认值来源于派生计算"的实现思路一脉相承。在 generators/base-application/internal/utils.ts 中可以看到类似逻辑:

skipClient: application.clientFrameworkReact || application.clientFrameworkVue,

即:当客户端框架是 React 或 Vue 时,skipClient会被派生为true——这正是"用户未显式定义时取合理默认"思想的现网形态(此处默认值与所选框架绑定)。同文件 L226 的另一处派生为:

skipClient: !application.clientFrameworkAngular,

可以看到,为了计算同一个skipClient,生成器内部需要在多处根据框架类型做反向推导,这正是排他语法造成的认知负担与重复逻辑的实证。

参考级说明(Reference-level explanation):配置落盘模型

在技术层面,RFC 要求:选项的存在性应当体现为 JSON 对象层级的属性。也就是说,无论选项是否被用户显式书写,最终进入生成器的配置 JSON 中都应有对应的布尔/值属性,而默认值同样被物化在 JSON 中。

输入:一段 JDL 示例

RFC 原文给出的 JDL 如下:

application { config: { baseName: "a" skipClient: true } } @ReadOnly entity E { } paginate * with pager except E

输出一:.yo-rc.json(应用级配置)

经转换后,生成器使用的(简化)JSON 对象如下:

{ "generator-jhipster": { "baseName": "a", "generateClient": true, "_comment_for_above": "see the change, and as it is a reasonable default, we may not have to specify it" } }

关键点:

  • 用户书写的skipClient: true正向表达generateClient: true(注意:RFC 原文此处为true,即"生成客户端"这一包含式语义,同时注明这是合理默认值,甚至可能无需显式落盘);
  • _comment_for_above是 RFC 用于说明语义的注释字段,示意"这是合理默认值,将来实现时或许不必显式写入"。

需要强调的是,RFC 中的generateClient: true是设计提案中的示意值,用于表达"包含式 + 合理默认值"的理念;当前仓库实际仍在应用级使用skipClient(默认值定义见 lib/jhipster/application-options.ts,类型为BOOLEAN,见 L271),迁移是 RFC 面向 V8 的演进目标。

输出二:.jhipster/E.json(实体级配置)

同一个 JDL 中,@ReadOnly注解与paginate * with pager except E全局选项转换到实体E的配置如下:

{ "name": "E", "config": { "queryMethods": true, "_comment_for_above": "reasonable default we may not have to specify it", "deleteMethods": false, "_comment_for_above": "result of the readonly option", "saveMethods": false, "_comment_for_above": "result of the readonly option", "generateEntityLayer": true, "_comment_for_above": "another reasonable default, ... as it is a reasonable default we may not have to specify it", "paginate": false, "_comment_for_above": "end user choice" } }

这个例子展示了实体级配置如何被"完全展开"为显式属性:

属性来源语义
queryMethodstrue合理默认值(用户未指定,可不落盘)
deleteMethodsfalse@ReadOnly注解的结果(只读实体不生成删除方法)
saveMethodsfalse@ReadOnly注解的结果(只读实体不生成保存方法)
generateEntityLayertrue另一个合理默认值(可不落盘)
paginatefalse最终用户决策(except E排除了实体 E)

可见,实体级配置中的每一项最终都被规范化成一个明确的布尔属性,其值要么来自合理默认,要么来自用户显式决策(注解、全局选项、实体级选项),从而让生成器消费配置时无需再做"是否被定义"的猜测。

实测:readOnly 在当前仓库中的落点

RFC 中的@ReadOnly场景在当前仓库中已存在对应实现。在 lib/jdl/core/built-in-options/unary-options.ts 中readOnly是一元选项;相关 JDL 测试夹具(如 lib/jdl/core/test-support/files/annotations.jdl)与解析转换测试(lib/jdl/converters/jdl-to-json/jdl-to-json-option-converter.spec.ts)均覆盖了注解到 JSON 配置的转换路径,感兴趣的读者可继续深入。

缺点(Drawbacks):Blueprint 扩展的代价

RFC 承认该方案存在缺点:带有额外选项的 Blueprint(蓝图)将不得不覆盖对象定义。为此,方案要求在 generator-core 层提供可覆盖的 API,允许 Blueprint 开发者在每一层(应用级、实体级等)添加自己的属性。

从仓库结构看,这一诉求与当前 Blueprint 机制的设计方向一致:generators/base/internal/blueprint.ts提供了蓝图解析与优先级处理逻辑,generators/base-application则承载了应用与实体的可覆盖定义(如 generators/base-application/entity.ts),为"每层可扩展属性"提供了基础的继承与覆盖骨架。

方案论证与备选(Rationale and alternatives):为什么排他语法不可行

RFC 指出,skip 语法存在多种解读方式,且与二元选项(binary options)不兼容

排他语法的歧义

RFC 原文给出的例子:

application { name: "a", clientFramework: angular } entity A { } skipController A // No entity HTML, No entity Component, No entity Front consumer Service, No entity Back ITest, No entity Front ITest, no entity front test: is this behavior really clear for end users? /* Illegal syntax */ skipService A with serviceImpl // We generate with serviceClass or we do not generate at all?

问题一目了然:

  • skipController A实际隐含了"不生成实体 HTML、不生成实体组件、不生成前端消费服务、不生成后端集成测试、不生成前端集成测试、不生成前端测试"等一长串连锁行为,但对最终用户而言,这些连锁效应完全不可见、不明确
  • skipService A with serviceImpl这种组合直接就是非法语法——语义上无法确定"是生成serviceClass还是什么都不生成"。

排他语法一旦与"带参数的二元选项"(如with serviceImpl)组合,就陷入表达力不足的困境。

包含式语法的对等表达

同一需求用包含式语法表达为:

application { name: "a", clientFramework: angular } entity A { } forms * except A clientServices * except A iTests * except A service * with serviceClass

每一条都是明确的正向指令:

  • forms * except A:所有实体都生成表单,除 A 以外;
  • clientServices * except A:所有实体都生成前端消费服务,除 A 以外;
  • iTests * except A:所有实体都生成集成测试,除 A 以外;
  • service * with serviceClass:所有实体都生成服务,且采用serviceClass类型。

对比可见,包含式语法把原来隐藏在skipController背后的多条隐式连锁行为显式化为可单独控制、可精确排除的选项,语义透明、可组合性强,且天然支持except这种面向集合的修饰。

先例(Prior art)

该 RFC 的先例即其关联 Issue:jhipster/generator-jhipster#14416,该 Issue 承载了"在生成器中使用包含式语法管理属性"这一需求的社区讨论与背景。

未来可能性(Future possibilities)

RFC-5 提出的包含式属性模型为生成器打开了三个明确的演进方向:

  1. 按需激活各层(layer):controller、forms、tests 等层次可以独立、按需地启用或关闭——这正是"把隐式连锁行为拆成显式开关"的自然延伸;
  2. 按需激活 C、R、U、D 能力:Create、Read、Update、Delete 四类 CRUD 能力可独立控制,与 RFC 中deleteMethodssaveMethodsqueryMethods的实体级展开模型直接呼应;
  3. 不止生成 REST,还能生成消息代理(message broker)的生产者与消费者:这将显著增强微服务架构的附加值——当"生成什么"由正向、显式的选项表达时,新增一类产出物(如消息消费者)只需新增对应的包含式选项,而无需发明新的排他语法。

总结:从 RFC 到源码的验证闭环

RFC-5 的核心主张可归纳为三点:选项一律正向表达、未定义时取合理默认、定义时尊重用户决策。本仓库源码从多个侧面印证了这一设计动机:

  • 排他选项的集中登记与默认值/类型定义见 lib/jhipster/application-options.ts(skipClient/skipServer/skipUserManagement默认false、类型BOOLEAN);
  • 一元选项(含skipClientreadOnly等)的语义定义见 lib/jdl/core/built-in-options/unary-options.ts;
  • skipClient同时作为应用选项与实体选项带来的归类复杂度见 lib/jdl/core/built-in-options/tokens/application-tokens.ts;
  • "未显式定义时按框架派生默认值"的现网实现见 generators/base-application/internal/utils.ts;
  • 生成器消费skipClient做条件判断的实例见 generators/client/command.ts(如主题选择仅在!config.skipClient时触发);
  • 相关转换与渲染行为的测试快照见 generators/client/generators/bootstrap/snapshots/generator.spec.ts.snap 与 generators/entity/snapshots/generator.spec.ts.snap。

对于 JHipster 生成器开发者和 Blueprint 维护者而言,RFC-5 提供了清晰的演进路线:新增选项时优先采用包含式命名(如generateXxxxxxMethods),并将默认值物化到.yo-rc.json.jhipster/*.json,从而逐步消除双重否定、减少分支逻辑,为按需分层生成、按需 CRUD 与消息驱动生成等能力铺平道路。

【免费下载链接】generator-jhipsterJHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.项目地址: https://gitcode.com/gh_mirrors/ge/generator-jhipster

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询