Master Data 主数据
主数据是结构,不是事实;唯一性由数据库保证,不靠应用层自觉。
职责
| 负责 | 不负责 |
|---|---|
| 实体唯一编码 | 自动概率匹配(相似度) |
| 版本历史(只追加) | 法人合并(实体族) |
| 合并 / 回滚(别名表) | 全量主数据同步 |
| 读侧自动解析 | 与外部 ERP 的编码映射 |
数据模型
| 集合 | 内容 | 约束 |
|---|---|---|
master_entities | 当前值 + version | path 唯一 |
master_entity_versions | 版本历史 | (path, version) 唯一 |
master_aliases | 合并记录 | aliasPath 在 status='active' 时唯一 |
路径:/master/{entityType}/{code},code 即实体主键。
唯一性由 path 唯一索引保证——应用层不重复校验。 这是主数据与业务域的根本区别:业务事实靠「仅追加 + actor」,主数据靠「数据库约束」。
合并
master_aliases: { aliasPath '/master/supplier/S-001'
→ canonicalPath '/master/supplier/S-1'
status: 'active' | 'reverted' }| 操作 | 行为 |
|---|---|
merge(from, to, reason) | 追加一条 active 别名;两边先解析到最终实体,避免长链 |
unmerge(aliasPath, reason) | 标成 reverted(不删,审计要看到「曾经合并过」) |
| 读取 | 自动跟随别名链到最终实体(带缓存与环检测) |
| 列表 | 排除被合并别名,否则同一实体出现两次 |
一行历史都不改:旧编码永远可解析,回滚只是翻一个状态位。
回滚后可以再合并到别处——aliasPath 只在 active 时唯一。
接口
| Method | Path | 说明 |
|---|---|---|
| GET | /api/master/:entityType | 按类型列出 |
| GET | /api/master/:entityType/:code | 读取(自动解析合并) |
| PUT | /api/master/:entityType/:code | upsert(版本 +1) |
| GET | /api/master/:entityType/:code/history | 版本历史 |
| POST | /api/master/merge | 合并 |
| POST | /api/master/unmerge | 回滚合并 |
| GET | /api/master/aliases | 生效中的合并记录 |
什么算同一个实体
| 层 | 谁解决 | 例子 |
|---|---|---|
| 语义层 | 业务规则 / 本体 | 「供应商 = 统一社会信用代码相同」 |
| 判定层 | 实体解析 | M-001 和 M001 是不是一个物料 |
| 执行层 | 本模块 | 合并后旧引用怎么办 |
本模块只做执行层:判据由调用方给,不自动判定。
边界
- 不做自动概率匹配(阈值需要真实数据)
- 不支持法人合并(两个供应商属于同一集团 ≠ 合并成一条)
- 别名链上限 32 层(防御脏写)
源码
libs/modules/src/
├── state/schemas/master-entity.schema.ts
├── state/schemas/master-alias.schema.ts
├── state/services/versioned-entity.store.ts 存储 + 透明解析
├── master-data/services/master-data.service.ts 合并 / 回滚
└── master-data/master-data.controller.ts 7 个端点