平台底座做久了会发现,"这个能不能用字典配"是一个高频但容易答错的问题。
判断标准只有一条:它是不是"纯枚举"
数据字典适合承载的是取值固定、没有独立生命周期、不需要被业务单据直接外键引用的枚举。典型如:客户分类、供应商分类、区域、行业、原因码、结算方式、币种代码。
不适合的是那些自己会变化、需要被引用、需要留历史的对象。
用这条标准检验几个常见候选:
| 候选 | 能否沉字典 | 理由 |
|---|---|---|
| 币种代码 | 可以 | ISO 4217 固定集合,业务只需读代码与精度 |
| 结算方式 | 可以 | 纯枚举,变化频率极低 |
| 客户/供应商分类 | 可以 | 分级靠编码前缀即可,不需要树 |
| 税码 | 不建议 | 税码是客户/供应商主数据上的属性,税率会随时间调整,且有申报口径 |
| 汇率 | 不可以 | 有时效性、需按币种对与日期查询、是计算依据 |
汇率为什么必须单独立表
汇率有两个字典给不了的能力:
- 时间维度。同一个币种对,不同日期的汇率不同,业务单据必须能取到"单据日期对应那一版"的汇率。字典项没有生效区间。
- 双向与精度。汇率是有方向和小数位要求的数值,不是标签。
用字典硬塞,最后一定会演化成"把汇率拼进字典项的扩展字段里再按日期解析",这比一开始就建表麻烦十倍。
所以汇率单独建表、单独页面管理,并且明确:单据在创建时快照汇率,不随后续汇率调整而变。
税码为什么不属于字典
税码看起来像枚举,实际上它是客户/供应商主数据上的采购/销售属性。同一个物料卖给不同客户,适用的税码可能不同;同一个供应商供不同物料,进项税码也可能不同。
把它做成全局字典,会导致业务在单据上无法区分"这条行项目的税率从哪来"。放在主数据上,来源唯一,追溯清晰。
这条结论在评审时被反复确认过:税码是主数据字段,不是字典项。
单级字典就够了,不升级成树
底座现有的字典是单级的:一个类型下挂若干项,没有父子层级,也没有生效区间。
评审时讨论过要不要升级:
- 加树结构:分类类数据(如物料分类)确实有多级需求;
- 加生效区间:看起来能给字典项做版本管理。
最后的决定是都不加,理由分别是:
分类不用树,用编码前缀。 三级分类用 01、0101、010101 这样的编码就能表达层级,查询时用 LIKE '01%' 即可。真正需要树结构的场景(比如组织架构、BOM)本身就不是字典该管的对象。为一个边缘需求给全平台字典加一套递归查询,收益不成立。
生效区间不加。 需要版本语义的对象(如 BOM)已经在各自的领域模型里用生效区间解决。字典项如果也要区间,说明它其实不是字典项,是业务实体。
这两条决定的本质是:底座的能力边界要窄而硬,业务差异放在业务模块里解决。底座一旦为了通用性引入复杂度,所有接入方都要为它付维护成本。
底座该提供什么,不该提供什么
在 super-admin 这个底座上,我划的线大致是:
| 能力 | 归属 |
|---|---|
| RBAC、组织、用户、菜单权限 | 底座 |
| 工作流引擎(四表模型、节点类型、转办/加签/撤回/抄送) | 底座 |
| 数据字典(单级枚举) | 底座 |
| 审计日志、操作留痕 | 底座 |
| 主数据(客户/供应商/物料/BOM) | 业务模块 |
| 汇率、税率、多币种结算 | 业务模块(单独表) |
| 业务单据状态机 | 业务模块 |
判断依据很简单:底座负责"谁能在系统里做什么",业务模块负责"业务长什么样"。 工作流之所以在底座,是因为几乎所有业务单据都需要审批,且审批的语义与业务无关;主数据之所以不在底座,是因为每类业务的字段、状态、引用约束都不同。
一条容易被忽略的约束:迁移与建表策略
底座的能力再清楚,落地时也会被工程细节绊一下。比如用 TypeORM 的项目里,data-source.ts 只手写了几个实体,DB_SYNC=false,migrations 目录又是空的——部署到空库时直接报实体元数据找不到。
处理办法是:实体注册改成 glob 扫描模块目录,保证所有实体都被加载;在没有迁移文件的情况下,先用 DB_SYNC=true 让同步建表跑通,再把迁移补起来。这个顺序不要反,反了就是空库启动失败。
小结
"能不能用字典"这个问题,答案不在字典本身,而在对象的语义:有没有时间维度、要不要被引用、会不会有独立生命周期。
底座的克制,换来的是每个业务模块可以按自己的语义建模;底座的"通用",最后往往变成所有人的技术债。