规则分类与建模:从混乱到有序

数据校验规则,说白了就是一堆「如果…就报错」的逻辑。但项目一大了,规则成百上千条,如果没有分类和建模,那就是一团乱麻。我见过不少团队,规则写在各种文档里、代码注释里、甚至钉钉聊天记录里——嗯,这种项目我一般不敢碰。

这一章,我们就来聊聊怎么给规则分门别类,以及怎么设计一个能支撑持续集成的规则元数据模型。

规则分类:四种核心类型

我个人习惯把校验规则分成四类。这四类不是拍脑袋想的,是我在几个大型数据平台项目里反复踩坑后总结出来的。

分类 核心关注点 典型场景 校验时机
格式规则 数据的外观是否符合规范 手机号11位、邮箱含@、日期格式YYYY-MM-DD 数据入库前
逻辑规则 数据内部是否自洽 开始日期 ≤ 结束日期、年龄≥0 数据入库后
业务规则 数据是否符合业务语义 订单金额=单价×数量、VIP用户等级≥3 业务处理中
引用规则 跨表/跨系统的数据一致性 用户ID在用户表中存在、订单号不重复 数据关联时

一个真实教训: 我曾经接手过一个项目,团队把所有规则都塞进「业务规则」一个分类里。结果呢?格式校验和业务逻辑混在一起,改一个手机号格式规则,居然要拉上业务方一起评审。分类不清,维护成本直接翻倍。

格式规则:最基础也最容易忽略

格式规则是最简单的,但往往也是出问题最多的。你想想看,一个日期字段,有的系统传「2024-01-01」,有的传「2024/01/01」,还有的传「2024年1月1日」。如果不做格式校验,下游系统直接崩溃。

我建议格式规则要做到「三明确」:

  • 明确字符集: 只允许数字?还是允许字母+数字?
  • 明确长度范围: 固定长度还是可变长度?最大最小是多少?
  • 明确模式模板: 用正则表达式还是枚举值?

小技巧: 格式规则尽量用正则表达式表达,不要写死代码。这样规则变更时,只需要改配置,不用改代码。我在一个金融项目里,把200多条格式规则全部抽成正则配置,上线后规则变更效率提升了80%。

逻辑规则:数据自洽的守护者

逻辑规则关注的是数据内部的「合理性」。比如一个订单的「发货时间」不能早于「下单时间」,一个用户的「注册日期」不能晚于「当前日期」。这些规则看起来简单,但一旦漏掉,后果很严重。

我记得有一次,数据仓库里跑出来一个报表,显示某用户「注册于2025年,但消费记录在2023年」。排查了半天,发现是逻辑校验没做好,ETL脚本里把时间字段搞混了。从那以后,我对逻辑规则格外重视。

逻辑规则通常涉及:

  • 字段间比较: A字段 > B字段?A字段 = B字段 + C字段?
  • 范围约束: 数值在[min, max]之间?日期在某个时间段内?
  • 枚举约束: 状态值必须在预定义的集合中?

业务规则:最复杂也最灵活

业务规则是四类规则里最「重」的。它直接反映业务逻辑,比如「VIP用户享受8折优惠」「订单金额超过1000元需要审批」。这类规则的特点是:变化频繁、依赖上下文、往往需要关联多个字段甚至多个系统。

我建议业务规则单独管理,不要和格式、逻辑规则混在一起。为什么呢?因为业务规则的变化频率远高于其他规则。你想想看,促销活动每周都在变,对应的业务规则也得跟着变。如果混在一起,每次改业务规则都要重新部署整个校验服务,太折腾了。

避坑指南: 我曾经在一个项目中,把业务规则写死在Java代码里。结果业务方每周提3次需求改规则,每次都要走完整的CI/CD流程。后来我改用规则引擎(Drools)把业务规则抽成配置文件,变更效率提升了10倍。记住:业务规则一定要可配置化。

引用规则:跨系统的信任桥梁

引用规则解决的是「数据在A系统存在,在B系统是否也存在」的问题。比如:订单表中的用户ID,必须在用户表中存在;交易记录中的产品编码,必须在产品目录中存在。

这类规则最头疼的地方在于:它依赖其他系统的数据。如果被引用的系统数据还没准备好,引用校验就会失败。所以引用规则通常需要设置「延迟校验」机制——等被引用数据到位后再校验。

引用规则的常见模式:

  • 存在性校验: 外键值在目标表中存在
  • 唯一性校验: 某个字段在目标表中不重复
  • 一致性校验: 两个系统中的同一字段值保持一致

规则元数据模型设计

分类搞清楚了,接下来就是建模。规则元数据模型,说白了就是「用什么样的数据结构来描述一条规则」。我设计过好几版模型,最终沉淀下来一个比较通用的版本。

-- 规则元数据核心表结构(简化版)
CREATE TABLE rule_metadata (
    rule_id          VARCHAR(64)    PRIMARY KEY,          -- 规则唯一ID
    rule_name        VARCHAR(128)   NOT NULL,              -- 规则名称
    rule_type        ENUM('FORMAT','LOGIC','BUSINESS','REFERENCE'), -- 规则分类
    rule_status      ENUM('ACTIVE','INACTIVE','DEPRECATED'),        -- 规则状态
    rule_priority    INT            DEFAULT 5,             -- 优先级 1-10
    rule_expression  TEXT           NOT NULL,              -- 规则表达式(如正则/SQL/脚本)
    rule_description TEXT,                                  -- 规则描述
    data_domain      VARCHAR(64),                          -- 所属数据域
    table_name       VARCHAR(128),                         -- 目标表
    column_name      VARCHAR(128),                         -- 目标字段
    error_code       VARCHAR(32),                          -- 错误码
    error_message    VARCHAR(512),                         -- 错误提示
    created_by       VARCHAR(64),                          -- 创建人
    created_at       TIMESTAMP      DEFAULT CURRENT_TIMESTAMP,
    updated_at       TIMESTAMP      DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

关键设计要点:

  • rule_expression: 这是核心字段,存储规则的具体逻辑。格式规则存正则,逻辑规则存SQL条件,业务规则存脚本或规则引擎DSL。
  • rule_status: 支持规则的生命周期管理。新规则先设为INACTIVE,测试通过后再激活。
  • error_code + error_message: 标准化错误输出,方便下游系统统一处理。

规则分类与元数据的关系

为了让你更直观地理解,我画了一张图。这张图展示了四种规则分类如何映射到元数据模型的不同字段上。

规则分类与元数据模型映射 格式规则 逻辑规则 业务规则 引用规则 规则元数据模型 rule_id / rule_name rule_type / rule_status rule_expression data_domain / table error_code / message created_by / timestamps 不同分类的规则,在 rule_expression 字段中使用不同的表达方式 格式→正则 | 逻辑→SQL条件 | 业务→脚本/DSL | 引用→跨表查询

规则元数据的扩展属性

除了核心字段,实际项目中还需要一些扩展属性。我列几个常用的:

扩展属性 类型 说明
rule_version INT 规则版本号,每次变更递增
effective_date DATE 规则生效日期
expiry_date DATE 规则失效日期(用于临时规则)
severity ENUM 严重级别:CRITICAL / WARNING / INFO
owner_team VARCHAR 规则负责人团队
source_system VARCHAR 规则来源系统

我的经验: 扩展属性不要一开始就设计得太全。我见过有人设计了50多个字段的元数据表,结果80%的字段都是空的。建议先设计核心字段,随着项目推进再逐步添加扩展属性。敏捷一点,别想着一步到位。

规则建模的常见误区

最后,分享几个我在项目中踩过的坑:

  • 误区一:规则和校验逻辑耦合太深。 规则元数据应该只描述「校验什么」,不描述「怎么校验」。校验引擎是另一层的事。
  • 误区二:忽略规则版本管理。 规则是会变的。没有版本管理,出了问题你都不知道是哪个版本的规则在生效。
  • 误区三:规则分类太细或太粗。 分类太细,管理成本高;分类太粗,规则混在一起难以维护。四种分类刚刚好。