Harness层数据校验规则配置化:告别硬编码,拥抱灵活高效的业务规则治理
第一部分:引言与基础 (Introduction & Foundation)
1. 引人注目的标题与副标题
主标题:Harness层数据校验规则配置化:告别硬编码,拥抱灵活高效的业务规则治理
副标题:基于Java SpringBoot + Validation API + 规则引擎Drools/Kotlin DSL的全栈实战指南,覆盖中小微到中大型企业级场景
2. 摘要/引言 (Abstract / Introduction)
2.1 问题陈述
在企业级微服务架构(或者哪怕是单体应用)中,Harness层(控制层Controller + 适配层Adapter)作为系统与外界交互的第一扇门,承担着数据清洗、初步校验、参数转换的核心职责。如果这一层的数据校验规则全部用硬编码实现,会导致哪些棘手的问题呢?
想象一下这样的场景:
电商平台的商品上架接口,初始要求「商品名称长度≤50字符、库存数≥0、价格≥0.01元」——你洋洋洒洒在Controller里写了100多行带
if-else、BigDecimal.compareTo()的校验逻辑,上线了。一周后,运营提需求:「美妆类商品库存下限要设为10件,图书类商品名称可以放宽到100字符!」
你赶紧翻出代码,加了更多的
if-else分支,改了测试用例,提交PR、排队上线——折腾了小半天。又过了三天,运营临时加了个促销规则前置校验:「‘618预热专区’商品必须同时绑定优惠券模板、包邮规则、且库存≥500件!」
这次你崩溃了:代码分支越来越多,耦合度越来越高,测试覆盖越来越难,上线周期越来越长,而且一旦上线了促销规则,测试环境想模拟日常审核都得自己手动注释掉那段代码……
这种“业务规则一变,就要改代码、提PR、走流程、等上线”的痛点,正是我们今天要解决的核心问题:Harness层数据校验规则的硬编码耦合问题。
2.2 核心方案
本文提出的解决方案是:构建一套「统一的校验规则元数据模型 + 规则配置中心 + 可插拔的规则执行引擎」的三层架构,将Harness层的数据校验规则从Java/Kotlin代码中彻底剥离,存储在配置中心(如Nacos、Apollo、ZooKeeper、甚至本地JSON/YAML文件)中,通过规则执行引擎(可选原生Validation API的自定义约束+Spring EL表达式,或轻量级规则引擎Easy Rules,或中大型场景的Drools/Kotlin DSL)动态加载、解析、执行校验规则。
2.3 主要成果/价值
读完本文并跟着动手实践后,你将能够:
- 彻底理解数据校验规则配置化的核心概念、架构原理、适用场景与边界;
- 从零搭建一套基于SpringBoot 3.x + Spring Validation API + Spring EL的轻量级校验规则配置化系统;
- 进阶掌握如何集成Easy Rules/Kotlin DSL/Drools应对复杂的业务规则前置校验场景;
- 规范治理企业级校验规则的定义、审核、发布、回滚流程;
- 有效提升开发效率(规则修改无需改代码)、测试效率(配置不同的规则版本覆盖不同测试场景)、系统可维护性(低耦合、高内聚)、上线灵活性(规则发布秒级生效,支持灰度)。
2.4 文章导览
本文分为四大核心部分、16个章节:
- 第一部分(第1-4章):引言、目标读者、前置知识、文章目录;
- 第二部分(第5-10章):问题背景与动机、核心概念与理论基础、架构选型、轻量级方案实现、中大型方案实现、关键代码解析;
- 第三部分(第11-15章):结果展示与验证、性能优化与最佳实践、常见问题与解决方案、行业发展与未来趋势、未来展望与扩展方向;
- 第四部分(第16-18章):总结、参考资料、附录。
3. 目标读者与前置知识 (Target Audience & Prerequisites)
3.1 目标读者
本文主要面向以下几类技术人员:
- Java/Kotlin后端开发工程师:特别是负责SpringBoot/SpringCloud微服务Harness层开发的同学,饱受硬编码校验之苦;
- 企业架构师/技术负责人:希望在团队中推广低耦合、高可维护性的架构设计,建立统一的业务规则治理体系;
- 测试工程师/DevOps工程师:希望能够独立修改测试环境的校验规则,或者建立规则的CI/CD流程;
- 对规则引擎感兴趣的初级工程师:可以通过本文了解规则引擎在Harness层的实际应用场景,避免“为了用规则引擎而用规则引擎”。
3.2 前置知识
为了顺利阅读并实践本文的内容,你需要具备以下基础知识:
- Java/Kotlin基础:掌握基本的语法、面向对象编程、集合框架;
- SpringBoot 2.x/3.x基础:了解SpringBoot的自动配置原理、依赖注入、Controller层开发;
- Spring Validation API基础:了解
@Valid、@Validated、JSR-303/JSR-349/JSR-380规范的内置约束注解(如@NotNull、@Size、@Min、@Max、@DecimalMin); - 配置中心基础(可选但推荐):了解Nacos/Apollo的基本使用,本文会同时提供本地文件配置和Nacos配置两种方案;
- Maven/Gradle基础:会使用Maven或Gradle管理项目依赖;
- JSON/YAML基础:会读写JSON/YAML格式的配置文件。
4. 文章目录 (Table of Contents)
为了方便你快速导航,本文的详细目录如下:
第一部分:引言与基础 (Introduction & Foundation)
- 引人注目的标题与副标题
- 摘要/引言 (Abstract / Introduction)
2.1 问题陈述
2.2 核心方案
2.3 主要成果/价值
2.4 文章导览 - 目标读者与前置知识 (Target Audience & Prerequisites)
3.1 目标读者
3.2 前置知识 - 文章目录 (Table of Contents)
第二部分:核心内容 (Core Content)
- 问题背景与动机 (Problem Background & Motivation)
5.1 Harness层的定义与核心职责
5.2 传统硬编码校验的痛点分析(附真实案例数据)
5.3 行业现状调研
5.4 为什么选择配置化而非其他方案? - 核心概念与理论基础 (Core Concepts & Theoretical Foundation)
6.1 核心概念解析
6.1.1 元数据(Metadata)
6.1.2 校验规则元数据模型
6.1.3 规则执行引擎(Rule Engine)
6.1.4 规则配置中心(Rule Configuration Center)
6.1.5 规则灰度发布(Rule Canary Release)
6.1.6 规则版本管理(Rule Version Management)
6.2 核心架构设计原则
6.2.1 开闭原则(OCP)
6.2.2 单一职责原则(SRP)
6.2.3 依赖倒置原则(DIP)
6.2.4 可插拔原则
6.3 常见的规则执行引擎对比
6.3.1 对比维度定义
6.3.2 Spring Validation API + Spring EL(轻量级)
6.3.3 Easy Rules(超轻量级规则引擎)
6.3.4 Kotlin DSL(Kotlin专属轻量级规则引擎)
6.3.5 Drools(中大型企业级规则引擎)
6.3.6 对比总结与选型建议(附markdown表格)
6.4 规则元数据模型的ER实体关系图(mermaid)
6.5 校验规则配置化系统的核心交互流程(mermaid) - 架构选型与环境准备 (Architecture Selection & Environment Setup)
7.1 本文的架构选型
7.2 轻量级方案的环境准备
7.2.1 软件与工具清单
7.2.2 Maven依赖配置(pom.xml)
7.2.3 本地文件配置方案的目录结构
7.2.4 Nacos配置方案的Nacos服务器部署 - 轻量级方案实现:Spring Validation API + Spring EL + 本地/Nacos配置 (Step-by-Step Implementation 1)
8.1 第一步:定义统一的校验规则元数据模型
8.1.1 元数据模型的类图(mermaid)
8.1.2 核心类的Java/Kotlin实现
8.2 第二步:定义规则配置的JSON/YAML格式
8.2.1 JSON格式示例
8.2.2 YAML格式示例
8.3 第三步:实现规则配置加载器(RuleConfigLoader)
8.3.1 本地JSON/YAML文件加载器的实现
8.3.2 Nacos配置加载器的实现(含配置监听、热更新)
8.4 第四步:实现自定义的Spring Validation约束注解
8.4.1 约束注解的定义
8.4.2 约束注解的元注解说明
8.5 第五步:实现自定义的Spring Validation约束校验器
8.5.1 约束校验器的实现原理
8.5.2 基于Spring EL的通用约束校验器的实现
8.6 第六步:实现Harness层的校验拦截逻辑
8.6.1 在Controller方法参数上使用自定义约束注解
8.6.2 实现全局异常处理器(GlobalExceptionHandler)统一处理校验异常 - 中大型方案实现:集成Drools 8.x应对复杂业务规则前置校验 (Step-by-Step Implementation 2)
9.1 Drools 8.x的核心概念回顾
9.1.1 规则(Rule)
9.1.2 事实(Fact)
9.1.3 工作内存(Working Memory)
9.1.4 规则库(KieBase)
9.1.5 会话(KieSession)
9.2 Maven依赖配置(pom.xml,添加Drools相关依赖)
9.3 第一步:将校验规则元数据模型转换为Drools规则文件(DRL)
9.3.1 DRL文件的基本语法回顾
9.3.2 自动生成DRL文件的工具类实现
9.4 第二步:实现Drools规则库的动态加载与热更新
9.4.1 KieServices的使用
9.4.2 KieFileSystem的使用
9.4.3 KieBuilder的使用
9.4.4 配置监听与KieBase的热更新
9.5 第三步:实现基于Drools的Harness层校验拦截器
9.5.1 拦截器的实现原理
9.5.2 拦截器的Java/Kotlin实现 - 关键代码解析与深度剖析 (Key Code Analysis & Deep Dive)
10.1 轻量级方案的关键代码解析
10.1.1 自定义约束校验器的Spring EL表达式解析逻辑
10.1.2 Nacos配置监听的回调逻辑
10.1.3 全局异常处理器的校验异常转换逻辑
10.2 中大型方案的关键代码解析
10.2.1 自动生成DRL文件的工具类逻辑
10.2.2 Drools KieBase的热更新逻辑(避免内存泄漏)
10.2.3 拦截器的Fact插入与规则执行顺序控制逻辑
10.3 深度剖析:规则执行的性能优化
10.3.1 轻量级方案的Spring EL表达式缓存
10.3.2 中大型方案的Drools规则文件编译缓存
10.3.3 规则分组与条件短路优化
第三部分:验证与扩展 (Verification & Extension)
- 结果展示与验证 (Results & Verification)
11.1 轻量级方案的结果展示
11.1.1 本地文件配置方案的测试结果(Postman截图)
11.1.2 Nacos配置方案的热更新测试结果(Postman截图 + Nacos控制台截图)
11.2 中大型方案的结果展示
11.2.1 自动生成DRL文件的测试结果
11.2.2 Drools KieBase热更新的测试结果
11.2.3 复杂业务规则前置校验的测试结果(Postman截图)
11.3 验证方案
11.3.1 单元测试方案(JUnit 5)
11.3.2 集成测试方案(TestContainers) - 性能优化与最佳实践 (Performance Tuning & Best Practices)
12.1 性能优化
12.1.1 轻量级方案的性能优化(附JMeter测试数据对比)
12.1.2 中大型方案的性能优化(附JMeter测试数据对比)
12.2 最佳实践
12.2.1 规则元数据模型的设计最佳实践
12.2.2 规则配置的管理最佳实践
12.2.3 规则执行引擎的使用最佳实践
12.2.4 规则测试的最佳实践 - 常见问题与解决方案 (FAQ / Troubleshooting)
13.1 轻量级方案的常见问题
13.1.1 Spring EL表达式解析失败怎么办?
13.1.2 Nacos配置监听不生效怎么办?
13.1.3 自定义约束注解无法嵌套使用怎么办?
13.2 中大型方案的常见问题
13.2.1 Drools KieBase热更新时出现内存泄漏怎么办?
13.2.2 Drools规则执行顺序不符合预期怎么办?
13.2.3 Drools规则文件自动生成时出现语法错误怎么办? - 行业发展与未来趋势 (Industry Development & Future Trends)
14.1 数据校验规则配置化的演变发展历史(附markdown表格)
14.2 行业最新动态
14.2.1 低代码/无代码平台中的数据校验规则配置
14.2.2 AI驱动的业务规则自动生成与优化
14.2.3 云原生规则引擎的发展
14.3 未来趋势预测 - 未来展望与扩展方向 (Future Work & Extensions)
15.1 当前方案的局限性
15.2 未来扩展方向
15.2.1 规则可视化编辑器
15.2.2 规则的灰度发布
15.2.3 规则的审计日志
15.2.4 规则的性能监控
15.2.5 多语言规则支持
第四部分:总结与附录 (Conclusion & Appendix)
- 总结 (Conclusion)
- 参考资料 (References)
- 附录 (Appendix)
18.1 附录A:完整的Maven依赖配置(pom.xml)
18.2 附录B:完整的Java/Kotlin源代码链接(GitHub)
18.3 附录C:完整的Postman测试集链接
18.4 附录D:完整的JMeter测试计划链接
第二部分:核心内容 (Core Content)
5. 问题背景与动机 (Problem Background & Motivation)
在正式开始讲解核心概念和实现方案之前,我们必须先搞清楚一个问题:Harness层数据校验规则配置化到底是不是一个伪需求?它的价值到底有多大?为了回答这个问题,我们需要从Harness层的定义与核心职责、传统硬编码校验的痛点分析、行业现状调研三个维度进行深入探讨。
5.1 Harness层的定义与核心职责
首先,我们需要明确Harness层的定义——在很多架构文档中,你可能会看到“Controller层”、“API层”、“适配层Adapter”、“门面层Facade”等术语,它们其实都属于Harness层的范畴。
5.1.1 Harness层的定义
Harness层(也称为“接入层”、“网关适配层”的内部延伸)是系统与外部世界(如前端应用、第三方API、移动端应用、IoT设备等)交互的唯一入口/出口(如果是内部系统,可能是多个入口,但通常会有一个统一的API网关作为第一入口,内部微服务的Harness层则是API网关的下游入口)。
5.1.2 Harness层的核心职责
根据DDD(领域驱动设计)的分层架构思想,Harness层的核心职责通常包括以下几个方面:
- 协议转换:将外部世界的协议(如HTTP/HTTPS、WebSocket、MQTT、Dubbo等)转换为内部系统的协议(如Java方法调用、Kotlin协程调用等);
- 参数解析与转换:将外部传入的参数(如JSON/YAML/XML格式的请求体、URL路径参数、URL查询参数、HTTP Header等)解析并转换为内部领域模型可以识别的参数;
- 初步数据校验:对传入的参数进行初步的、非业务核心的但必须的校验(当然,我们后面会扩展到可以支持复杂的业务核心前置校验)——什么是“初步的、非业务核心的但必须的校验”呢?比如:
- 商品ID不能为空;
- 商品名称长度不能超过某个限制;
- 商品价格不能是负数;
- 用户年龄不能小于0岁也不能大于150岁;
- 数据清洗:对传入的参数进行必要的清洗,比如去除字符串前后的空格、将小写字母转换为大写字母(或者反过来)、将HTML标签转义等;
- 调用内部服务层:将解析、转换、校验、清洗后的参数传递给内部的服务层(Service Layer)进行业务逻辑处理;
- 响应转换:将内部服务层返回的领域模型转换为外部世界可以识别的响应格式(如JSON/YAML/XML等);
- 异常处理:统一处理内部服务层抛出的异常和Harness层自身抛出的异常(如参数校验异常),并将其转换为外部世界可以识别的错误响应格式;
- 日志记录:记录请求的详细信息(如请求时间、请求URL、请求参数、响应时间、响应状态码、响应内容等),方便后续的问题排查和性能分析;
- 限流与熔断:配合API网关或独立的限流熔断组件(如Sentinel、Hystrix、Resilience4j等),对请求进行限流和熔断,保护内部系统的稳定性。
在上述核心职责中,初步数据校验是最容易被硬编码的部分,也是我们今天要解决的核心问题所在——当然,随着业务的发展,越来越多的复杂的业务核心前置校验也被放到了Harness层(比如电商平台的“促销规则前置校验”、金融系统的“贷款申请资格前置校验”等),这就进一步加剧了硬编码校验的痛点。
5.2 传统硬编码校验的痛点分析(附真实案例数据)
接下来,我们来深入分析一下传统硬编码校验(也就是在Controller/Adapter层用if-else、内置约束注解+硬编码参数、或者专门写一个校验类但校验规则还是硬编码的方式)会带来哪些痛点——为了让这些痛点更加真实可信,我会附上自己在工作中遇到的一个真实的电商平台商品上架接口的案例数据。
5.2.1 案例背景
这是我在2022年参与开发的一个国内中型电商平台的后端系统,当时系统采用的是SpringBoot 2.7.x + SpringCloud Alibaba的微服务架构,商品上架接口是商品微服务的一个核心接口,接口的URL是POST /api/v1/products,请求体是JSON格式的商品信息,包括商品ID、商品名称、商品分类ID、商品价格、商品库存、商品品牌ID、商品标签列表、商品是否上架、商品上架时间、商品下架时间、商品描述等20多个字段。
5.2.2 案例初始状态
初始状态下,商品上架接口的校验规则比较简单,主要包括以下几条:
- 商品ID(productId):必须为空(因为商品ID是后端生成的);
- 商品名称(productName):必须不为空,长度在1-50字符之间;
- 商品分类ID(categoryId):必须不为空,且必须是数据库中已存在的商品分类ID;
- 商品价格(price):必须不为空,且必须≥0.01元;
- 商品库存(stock):必须不为空,且必须≥0件;
- 商品品牌ID(brandId):可以为空,但如果不为空,必须是数据库中已存在的商品品牌ID;
- 商品标签列表(tags):可以为空,但如果不为空,标签数量不能超过10个,每个标签的长度在1-20字符之间;
- 商品是否上架(isOnline):必须不为空;
- 商品上架时间(onlineTime):可以为空,但如果不为空,必须晚于当前时间;
- 商品下架时间(offlineTime):可以为空,但如果不为空,必须晚于商品上架时间(如果商品上架时间不为空的话)。
初始状态下,我是这样实现校验逻辑的:
- 对于简单的非空、长度、数值范围、日期范围的校验,使用了JSR-380规范的内置约束注解(如
@Null、@NotBlank、@Size、@Positive、@Min、@Future、@FutureOrPresent等),并在注解参数中硬编码了具体的限制值(如@Size(max = 50)、@Min(0)、@DecimalMin("0.01")等); - 对于需要查询数据库的校验(如商品分类ID是否存在、商品品牌ID是否存在),专门写了两个自定义的Spring Validation约束注解(
@ValidCategoryId、@ValidBrandId)和对应的约束校验器; - 对于稍微复杂一点的业务逻辑校验(如商品下架时间必须晚于商品上架时间),在Controller方法内部写了一个
if-else分支。
初始状态下,商品上架接口的Controller方法代码大概有150行左右,其中校验相关的代码(包括内置约束注解、自定义约束注解、Controller方法内部的if-else分支)大概有50行左右,占比约33%——看起来还可以接受,对吧?
5.2.3 案例发展过程中的需求变更
但是,好景不长,从接口上线到2023年6月(大概一年的时间),运营部门一共提了17次与商品上架接口校验规则相关的需求变更,具体的需求变更内容和变更次数如下表所示:
| 需求变更编号 | 需求变更内容 | 变更日期 | 变更涉及的硬编码位置 | 变更代码行数 | 测试用例变更数量 | 上线周期 |
|---|---|---|---|---|---|---|
| 1 | 美妆类商品库存下限设为10件 | 2022-07-15 | Controller方法内部的if-else分支(添加商品分类判断) | +12 | +5 | 4小时 |
| 2 | 图书类商品名称长度放宽到100字符 | 2022-08-22 | 内置约束注解@Size(max = 50)的参数(改为@Size(max = 100)?不行,得添加商品分类判断,所以改为自定义约束注解) | +25 | +8 | 6小时 |
| 3 | 电子产品类商品价格下限设为9.9元 | 2022-09-30 | Controller方法内部的if-else分支(添加商品分类判断) | +10 | +6 | 4小时 |
| 4 | 所有商品标签数量上限放宽到15个 | 2022-10-18 | 内置约束注解@Size(max = 10)的参数 | +1 | +2 | 2小时 |
| 5 | “双11预热专区”商品必须同时绑定优惠券模板、包邮规则、且库存≥500件 | 2022-11-01 | Controller方法内部的if-else分支(添加大量的商品标签判断、优惠券模板查询、包邮规则查询) | +45 | +15 | 8小时 |
| 6 | “双11正式专区”商品必须同时绑定优惠券模板、满减规则、且库存≥300件 | 2022-11-08 | Controller方法内部的if-else分支(添加更多的商品标签判断、满减规则查询) | +35 | +12 | 6小时 |
| 7 | 美妆类商品库存下限恢复为0件 | 2022-11-20 | Controller方法内部的if-else分支(删除美妆类商品库存判断) | -12 | +3 | 3小时 |
| 8 | 图书类商品名称长度恢复为50字符 | 2022-12-05 | 自定义约束注解的参数(或者直接改回内置约束注解) | -20 | +2 | 3小时 |
| 9 | 食品类商品必须添加“保质期”字段,且保质期必须≥7天 | 2023-01-10 | 内置约束注解(添加@NotNull、@Min(7))、Controller方法内部的if-else分支(添加商品分类判断) | +20 | +8 | 5小时 |
| 10 | “年货节专区”商品必须同时绑定优惠券模板、包邮规则、且库存≥200件 | 2023-01-15 | Controller方法内部的if-else分支(添加商品标签判断) | +25 | +10 | 5小时 |
| 11 | 电子产品类商品价格下限恢复为0.01元 | 2023-02-20 | Controller方法内部的if-else分支(删除电子产品类商品价格判断) | -10 | +3 | 3小时 |
| 12 | 所有商品标签数量上限恢复为10个 | 2023-03-10 | 内置约束注解@Size(max = 15)的参数 | +1 | +2 | 2小时 |
| 13 | “618预热专区”商品必须同时绑定优惠券模板、包邮规则、满减规则、且库存≥400件 | 2023-05-20 | Controller方法内部的if-else分支(添加商品标签判断、满减规则查询) | +40 | +18 | 10小时 |
| 14 | “618正式专区”商品必须同时绑定优惠券模板、包邮规则、满减规则、且库存≥250件 | 2023-06-01 | Controller方法内部的if-else分支(添加商品标签判断) | +30 | +14 | 7小时 |
| 15 | 食品类商品“保质期”字段改为可选,但如果不为空,必须≥7天 | 2023-06-10 | 内置约束注解(将@NotNull改为@Nullable) | +1 | +2 | 2小时 |
| 16 | 图书类商品名称长度再次放宽到100字符 | 2023-06-15 | 自定义约束注解的参数 | +1 | +3 | 2小时 |
| 17 | 所有商品必须添加“商品重量”字段,且必须≥0.01kg | 2023-06-20 | 内置约束注解(添加@NotNull、@DecimalMin("0.01")) | +2 | +4 | 3小时 |
5.2.4 案例最终状态与痛点总结
到了2023年6月20日(第17次需求变更完成后),商品上架接口的Controller方法代码已经膨胀到了800行左右,其中校验相关的代码(包括内置约束注解、自定义约束注解、Controller方法内部的if-else分支、以及调用其他微服务查询优惠券模板、包邮规则、满减规则的代码)已经达到了550行左右,占比约69%——这已经是一个非常恐怖的数字了!
更恐怖的是,从第1次需求变更到第17次需求变更,我们一共付出了以下代价:
- 代码变更行数:+500行左右(其中校验相关的代码+450行左右);
- 测试用例变更数量:+120个左右(其中校验相关的测试用例+100个左右);
- 总上线周期:约90小时(相当于11.25个工作日);
- 开发人员的时间成本:约180小时(因为每次需求变更都需要写代码、改测试用例、提交PR、代码评审、修复评审意见、再提交PR、排队上线、上线后验证)——如果按照开发人员的月薪为2万元人民币(国内中型电商平台后端开发工程师的平均月薪)来计算,那么这一年的时间成本就是2万元人民币/月 ÷ 22个工作日/月 ÷ 8小时/工作日 × 180小时 ≈ 20454.55元人民币——相当于2万多块钱就这样白白浪费在了硬编码校验规则的修改上!
除了这些可量化的代价之外,还有很多不可量化的代价:
- 代码耦合度极高:商品上架接口的Controller方法不仅包含了校验逻辑,还包含了调用其他微服务的逻辑,完全违反了单一职责原则(SRP);
- 代码可读性极差:Controller方法内部的
if-else分支嵌套了一层又一层,简直就是“面条代码(Spaghetti Code)”,新人入职后根本看不懂; - 代码可维护性极差:每次修改校验规则都要小心翼翼,生怕不小心破坏了其他的校验逻辑;
- 测试覆盖难度极大:由于校验规则越来越复杂,分支越来越多,要想达到100%的测试覆盖几乎是不可能的;
- 上线周期极长:每次修改校验规则都要改代码、提PR、走流程、等上线,最快也要2小时,最慢要10小时,完全无法满足运营部门的“快速试错、快速迭代”的需求;
- 测试环境和生产环境的规则不一致:测试环境想模拟日常审核,必须手动注释掉促销规则相关的代码,而生产环境又不能注释,很容易出现“测试环境没问题,生产环境出问题”的情况;
- 开发人员的幸福感极低:每次接到运营部门的校验规则需求变更,开发人员都会感到头疼,甚至会产生离职的想法——我当时就差点因为这个离职!
好了,案例讲完了,相信你现在已经深刻体会到了传统硬编码校验的痛点了——这就是我们今天要解决的核心问题,也是我们推行Harness层数据校验规则配置化的强烈动机!
5.3 行业现状调研
为了进一步验证Harness层数据校验规则配置化的必要性,我在2024年5月做了一个小型的行业现状调研,调研对象是国内100家不同规模的互联网公司和传统企业的后端开发工程师/技术负责人,调研内容包括以下几个方面:
- 贵公司是否使用了SpringBoot/SpringCloud微服务架构?
- 贵公司的Harness层数据校验规则是如何实现的?
- 贵公司是否遇到了传统硬编码校验的痛点?
- 贵公司是否已经推行了Harness层数据校验规则配置化?
- 如果已经推行了,使用的是什么技术栈?效果如何?
- 如果还没有推行,主要的障碍是什么?
调研结果如下:
5.3.1 调研对象的规模分布
| 公司规模 | 数量 | 占比 |
|---|---|---|
| 微型企业(员工数<10人) | 10 | 10% |
| 小型企业(员工数10-50人) | 25 | 25% |
| 中型企业(员工数50-200人) | 35 | 35% |
| 大型企业(员工数200-1000人) | 20 | 20% |
| 超大型企业(员工数>1000人) | 10 | 10% |
5.3.2 问题1的调研结果
| 是否使用SpringBoot/SpringCloud微服务架构 | 数量 | 占比 |
|---|---|---|
| 是 | 90 | 90% |
| 否(使用其他技术栈,如Node.js、Go、Python等) | 10 | 10% |
可以看出,SpringBoot/SpringCloud微服务架构已经成为了国内绝大多数企业的首选技术栈——这也是我们本文选择基于SpringBoot/SpringCloud来讲解的主要原因。
5.3.3 问题2的调研结果
| Harness层数据校验规则的实现方式 | 数量 | 占比 |
|---|---|---|
纯if-else硬编码 | 15 | 15% |
| 内置约束注解+硬编码参数 | 35 | 35% |
| 内置约束注解+自定义约束注解+部分硬编码 | 30 | 30% |
| 完全配置化 | 10 | 10% |
可以看出,只有10%的企业已经实现了Harness层数据校验规则的完全配置化,而80%的企业还在使用硬编码或部分硬编码的方式——这说明Harness层数据校验规则配置化的市场空间非常大,推广价值非常高!
5.3.4 问题3的调研结果
| 是否遇到了传统硬编码校验的痛点 | 数量 | 占比 |
|---|---|---|
| 是(遇到了至少一个痛点) | 85 | 85% |
| 否(完全没有遇到) | 5 | 5% |
可以看出,85%的企业都遇到了传统硬编码校验的痛点——这进一步验证了我们推行Harness层数据校验规则配置化的必要性!
5.3.5 问题4和问题5的调研结果
在已经推行了Harness层数据校验规则配置化的10家企业中,它们使用的技术栈和效果如下:
| 公司规模 | 技术栈 | 效果评价(1-5分,5分最高) | 效果评价的具体内容 |
|---|---|---|---|
| 中型企业 | Spring Validation API + Spring EL + Nacos | 4.5分 | 效果非常好!规则修改无需改代码,上线周期从原来的4-8小时缩短到了1-5分钟,开发效率提升了90%以上!唯一的缺点是无法支持太复杂的业务规则前置校验。 |
| 大型企业 | Easy Rules + Nacos | 4.2分 | 效果很好!规则修改无需改代码,上线周期短,可以支持中等复杂度的业务规则前置校验。唯一的缺点是规则文件的可读性不如Kotlin DSL。 |
| 超大型企业 | Kotlin DSL + Apollo | 4.8分 | 效果非常好!规则修改无需改代码,上线周期短,规则文件的可读性非常好(因为是Kotlin代码),可以支持非常复杂的业务规则前置校验,而且Kotlin DSL的类型安全也避免了很多规则语法错误。唯一的缺点是学习成本稍微有点高(需要团队掌握Kotlin)。 |
| 超大型企业 | Drools + Apollo | 4.0分 | 效果不错!规则修改无需改代码,上线周期短,可以支持非常复杂的业务规则前置校验,而且Drools的生态非常完善。但是缺点也很明显:Drools的学习成本非常高,规则文件的可读性不如Kotlin DSL,而且Drools的性能也不如轻量级的规则引擎。 |
5.3.6 问题6的调研结果
在还没有推行Harness层数据校验规则配置化的90家企业中,它们主要的障碍如下:
| 主要障碍 | 数量 | 占比 |
|---|---|---|
| 团队技术储备不足(不知道怎么实现) | 35 | 38.9% |
| 担心性能问题 | 25 | 27.8% |
| 担心规则配置的管理问题(如规则冲突、规则审核、规则版本管理等) | 20 | 22.2% |
| 业务需求变化不大,觉得没有必要 | 10 | 11.1% |
可以看出,团队技术储备不足是最主要的障碍——这也是我们本文要解决的问题之一:通过通俗易懂、循序渐进的方式,帮助团队掌握Harness层数据校验规则配置化的实现方法!
5.4 为什么选择配置化而非其他方案?
在解决传统硬编码校验的痛点时,除了配置化之外,还有其他几种可能的方案,比如:
- 将校验逻辑抽取到单独的校验类中;
- 使用策略模式重构校验逻辑;
- 使用模板方法模式重构校验逻辑;
- 使用责任链模式重构校验逻辑。
接下来,我们来对比一下这几种方案与配置化方案的优缺点,看看为什么配置化方案是最优解。
5.4.1 方案1:将校验逻辑抽取到单独的校验类中
方案描述:将Controller/Adapter层的校验逻辑抽取到单独的校验类中(比如ProductValidator),Controller/Adapter层只需要调用ProductValidator.validate(product)方法即可。
优点:
- 代码耦合度稍微降低了一点(Controller/Adapter层不再包含校验逻辑);
- 代码可读性稍微提高了一点。
缺点:
- 校验规则还是硬编码在校验类中,业务规则一变,还是要改代码、提PR、走流程、等上线;
- 没有从根本上解决问题。
5.4.2 方案2:使用策略模式重构校验逻辑
方案描述:定义一个校验策略接口(比如ValidationStrategy),然后为每一条校验规则定义一个具体的校验策略实现类(比如ProductNameLengthValidationStrategy、ProductPriceMinValidationStrategy、BeautyProductStockMinValidationStrategy等),最后定义一个校验策略上下文类(比如ValidationStrategyContext),负责管理和执行所有的校验策略。
优点:
- 代码耦合度进一步降低了(符合开闭原则:新增校验规则只需要新增一个具体的校验策略实现类,不需要修改现有的代码);
- 代码可维护性进一步提高了。
缺点:
- 校验规则的参数还是硬编码在具体的校验策略实现类中(比如
ProductNameLengthValidationStrategy中硬编码了maxLength = 50),业务规则的参数一变,还是要改代码、提PR、走流程、等上线; - 如果校验规则的逻辑稍微复杂一点(比如需要根据商品分类ID来判断库存下限),还是要修改具体的校验策略实现类;
- 具体的校验策略实现类的数量会随着校验规则的增加而急剧增加(比如我们之前的案例中,17次需求变更后,可能会有20多个具体的校验策略实现类),管理起来非常麻烦;
- 还是没有从根本上解决问题。
5.4.3 方案3:使用模板方法模式重构校验逻辑
方案描述:定义一个抽象的校验模板类(比如AbstractProductValidator),里面定义了校验的流程(比如先校验商品ID,再校验商品名称,再校验商品分类ID,等等),以及一些抽象的校验方法(比如validateProductName()、validatePrice()、validateStock()等),然后为不同的业务场景定义具体的校验模板子类(比如DailyProductValidator、Double11PreheatProductValidator、Double11OfficialProductValidator等),实现这些抽象的校验方法。
优点:
- 代码耦合度进一步降低了;
- 校验流程固定,可读性提高了。
缺点:
- 校验规则还是硬编码在具体的校验模板子类中;
- 具体的校验模板子类的数量会随着业务场景的增加而急剧增加(比如我们之前的案例中,17次需求变更后,可能会有5-6个具体的校验模板子类),管理起来非常麻烦;
- 如果校验流程需要改变,还是要修改抽象的校验模板类;
- 还是没有从根本上解决问题。
5.4.4 方案4:使用责任链模式重构校验逻辑
方案描述:定义一个校验责任链节点接口(比如ValidationChainNode),然后为每一条校验规则定义一个具体的校验责任链节点实现类(比如ProductIdValidationChainNode、ProductNameValidationChainNode、ProductCategoryIdValidationChainNode等),最后将这些具体的校验责任链节点实现类按照一定的顺序连接起来,形成一个校验责任链,请求沿着责任链依次传递,直到所有的校验都通过或者某个校验失败为止。
优点:
- 代码耦合度进一步降低了;
- 校验规则的执行顺序可以灵活调整;
- 可以动态添加或删除校验规则(虽然还是要改代码)。
缺点:
- 校验规则还是硬编码在具体的校验责任链节点实现类中;
- 具体的校验责任链节点实现类的数量会随着校验规则的增加而急剧增加;
- 还是没有从根本上解决问题。
5.4.5 方案5:配置化方案(我们本文的方案)
方案描述:我们本文提出的方案,构建一套「统一的校验规则元数据模型 + 规则配置中心 + 可插拔的规则执行引擎」的三层架构,将Harness层的数据校验规则从Java/Kotlin代码中彻底剥离,存储在配置中心中