导言:事件驱动之后,数据对不上怎么办
上一篇文章我们聊了事件驱动架构。它的核心思路很简单:模块之间不再直接调用,而是通过事件来通信。订单创建了,发个事件;库存扣了,发个事件;财务记账了,也发个事件。各模块各干各的,互不阻塞。
这个思路在解耦上很有效。但有一类问题,事件驱动本身解决不了。
假设销售系统里有一个客户,叫"华东智联科技有限公司"。财务系统收到事件后,在自己的库里找这个客户,却发现记成了"华东智联公司"。客服系统里又有一条"华东智联上海分公司"。
三个名字看起来都像同一家公司,但系统里的编码各不相同。
这时候问题来了:信用额度该查哪个客户?发票该开给哪个抬头?应收账款该挂在哪个编号下面?
事件驱动解决了"消息怎么传",但它不能保证"传过去的数据是对的"。
这就是我们今天要聊的主题:主数据管理(MDM, Master Data Management)。我的结论是:如果各模块对"客户是谁""商品是什么"这些基础认知都不一致,再先进的架构也只是把错误传得更快而已。
主数据 vs 交易数据:两种完全不同的数据
在谈 MDM 之前,我们必须先区分两类数据。这个区分是理解一切的基础。
业务定义:
- 主数据(Master Data):描述业务实体的静态数据,变化频率低,被多个模块共享。例如客户、供应商、商品、会计科目、组织架构。
- 交易数据(Transactional Data):描述业务事件的动态数据,每天都在产生和变化。例如采购订单、销售订单、入库单、发票、付款记录。
你可以把主数据理解成企业的"员工花名册",把交易数据理解成"每天的考勤打卡记录"。
花名册上的人不常变,但每天的打卡记录都在新增。如果花名册上"张三"的工号不一致,那么不管打卡系统多先进,张三的考勤统计都会出错。
下面这张表,帮你快速建立两者的对比认知:
| 维度 | 主数据 | 交易数据 |
|---|---|---|
| 变化频率 | 慢,按月或按季度更新 | 快,每天产生大量新记录 |
| 数据量 | 少,通常几千到几万条 | 多,可达百万甚至上亿条 |
| 共享范围 | 跨模块共享,全局唯一 | 通常归属单一模块或流程 |
| 错误代价 | 一条错误影响所有关联流程 | 一条错误通常只影响单笔业务 |
这个对比说明一件事:主数据虽然量小,但牵一发而动全身。 一个客户编码错误,可能导致订单、信用、发票、收款全链路的混乱。
ERP 中的五大主数据
在 ERP 系统里,有五类主数据是最核心、最基础的。它们几乎出现在每一个业务流程中。
1. 客户主数据
业务定义:客户主数据是对"企业在跟谁做生意"的统一描述,包含客户的身份信息、属性信息和关系信息。
我们在第 12 篇文章里已经详细讲过 CRM 作为客户主数据底座的三层模型——身份层、属性层、关系层。那篇文章的核心结论是:CRM 不是销售工具,而是 O2C 的数据地基。 如果对这个三层模型感兴趣,可以回看第 12 篇。
客户主数据的核心字段包括:客户编码、客户名称、统一社会信用代码、客户类型、信用额度、付款条件、开票信息、联系人等。
2. 供应商主数据
业务定义:供应商主数据是对"企业向谁采购"的统一描述,是 P2P 流程的起点。
在采购流程中,采购订单必须明确指向一个供应商。如果同一个供应商在系统里有多个编号,采购就无法准确统计该供应商的历史交易、履约质量、应付账款余额。
核心字段包括:供应商编码、供应商名称、统一社会信用代码、供应商分类、付款银行信息、结算币种、账期、资质有效期等。
3. 商品主数据
业务定义:商品主数据是对"企业在买卖什么"的统一描述,是库存管理和成本核算的基础。
第 5 篇文章里,我们详细拆解了 SPU 和 SKU 的概念。SPU 是"产品族",SKU 是"具体可管理库存的商品"。所有的采购、入库、出库、盘点、销售,最终都要落实到 SKU 级别。
商品主数据的核心字段包括:SKU 编码、SPU 编码、商品名称、规格属性、计量单位、默认成本价、默认售价、税码、存储条件等。
4. 会计科目主数据
业务定义:会计科目主数据是对"企业用什么口径记账"的统一描述,是财务核算的语言体系。
每一笔业务发生之后,最终都要翻译成会计分录。而会计分录里的"借什么、贷什么",完全取决于科目主数据的定义。如果不同模块对"库存商品"这个科目的编码理解不一致,总账和业务系统的对账就会永远对不平。
核心字段包括:科目编码、科目名称、科目类别(资产 / 负债 / 权益 / 收入 / 成本)、余额方向、层级关系、是否启用辅助核算等。
5. 组织架构主数据
业务定义:组织架构主数据是对"企业由谁来做"的统一描述,是权限隔离和数据归属的依据。
一张采购订单属于哪个采购部,一笔销售回款归属于哪个事业部,一份报表汇总到哪个法人主体,这些都依赖组织架构主数据。
核心字段包括:组织编码、组织名称、组织类型(公司 / 部门 / 团队)、上级组织、法人标识、生效日期、状态等。
这五类主数据,像五根柱子一样撑起了 ERP 的全部业务流程。任何一根柱子歪了,上面的流程都会跟着倾斜。
为了帮你快速建立全貌认知,下面这张表汇总了五大主数据的关键信息:
| 主数据类型 | 归属模块 | 核心字段 | 消费模块 |
|---|---|---|---|
| 客户 | CRM | 编码、名称、信用额度、付款条件 | 销售、财务、客服 |
| 供应商 | SRM / 采购 | 编码、名称、银行信息、账期 | 采购、财务、库存 |
| 商品 | 商品中心 | SKU 编码、规格、成本价、售价 | 采购、库存、销售、财务 |
| 会计科目 | 财务总账 | 科目编码、名称、类别、方向 | 财务、所有业务模块 |
| 组织架构 | 人力 / 组织管理 | 组织编码、类型、上级组织 | 全模块(权限与归属) |
数据治理:谁来创建、谁来维护、谁来审批
主数据的问题,从来不只是"技术问题",更是"治理问题"。
技术可以保证数据同步的时效性,但无法回答"谁有权修改客户信用额度""供应商银行账户变了要经过谁审批"这类问题。
一个常见的主数据生命周期,通常包含以下阶段:
graph LR
A[申请创建] --> B[数据初审]
B --> C[标准审核]
C --> D[正式发布]
D --> E[日常维护]
E --> F[变更审批]
F --> G[归档停用]
每个阶段的职责划分,决定了主数据的质量底线。
申请创建:通常由业务部门发起。销售要新增一个大客户,采购要引入一个新供应商,商品部要上架一款新品。
数据初审:由主数据管理员或数据质量团队执行,检查必填字段是否完整、编码是否符合规范、是否与已有数据重复。
标准审核:对于敏感字段或高价值主数据,需要跨部门审核。例如新客户信用额度的设定,通常需要财务部门参与。
正式发布:审核通过后,主数据进入"生效"状态,各下游系统才能读取和使用。
日常维护:不同字段由不同部门维护。销售维护联系人信息,财务维护开票和付款信息,这是最常见的分工模式。
变更审批:关键字段的修改需要重新审批。例如客户信用额度的上调、供应商银行账户的变更,通常不能由业务人员直接修改。
归档停用:当主数据不再使用时,不应直接删除,而是标记为"停用"。因为历史交易数据可能还在引用它,删除会导致数据断层。
这个流程看起来有些繁琐,但它回答了一个核心问题:主数据不是谁都能改的花名册,而是一份有审批、有记录、有责任的正式档案。
主数据同步的三种模式
当主数据在一个系统里创建之后,其他系统怎么知道?这就是主数据同步要解决的问题。
在实践中,有三种最常见的同步模式。
模式一:主从复制
一个系统被明确指定为"主数据源",其他系统作为"从节点",定期或实时从主节点同步数据。
例如:CRM 是客户主数据的唯一源头,销售系统、财务系统、客服系统都定期从 CRM 同步客户信息。各从节点只读,不写。
这种模式最简单,一致性也最好。但它要求企业有明确的"谁做主"的决策。
模式二:发布订阅
主数据发生变更时,主动发布一个事件,订阅了该事件的系统自行消费。
例如:客户信用额度被修改后,CRM 发布一个"客户信用已变更"的事件。财务系统、风控系统、销售系统都订阅了这个事件,各自更新自己的本地缓存。
这种模式解耦性好,适合微服务架构。但它引入了"最终一致性"的问题——订阅方可能不是立即收到变更,中间有一个短暂的时间窗口数据不一致。
模式三:API 网关
不强制各系统同步数据到本地,而是提供一个统一的主数据查询服务。各业务系统在需要时,实时调用 API 获取最新数据。
例如:订单系统创建订单时,不依赖本地缓存的客户信息,而是实时调用主数据服务,获取客户的最新信用额度和付款条件。
这种模式的数据一致性最好,但增加了实时调用的依赖和延迟。如果主数据服务不可用,业务流程可能会卡住。
三种模式各有优劣,适用的场景也不同。下面这张决策表可以帮助你快速判断:
| 场景 | 推荐模式 | 优点 | 缺点 |
|---|---|---|---|
| 数据变化少,一致性要求高 | 主从复制 | 简单稳定,易排查 | 耦合度较高 |
| 微服务架构,模块自治 | 发布订阅 | 解耦好,扩展性强 | 有最终一致性窗口 |
| 数据变化频繁,实时性要求高 | API 网关 | 数据最新,无同步延迟 | 强依赖中心服务 |
在实际工程中,很多企业会混合使用。例如客户主数据用主从复制保证稳定,库存数量用发布订阅实时推送,信用额度检查用 API 网关实时查询。
数据质量:重复、不一致与过时
即使有了同步机制,主数据仍然会出现三类典型的质量问题。
问题一:重复数据
同一个客户,通过官网注册了一次,通过销售手工录入了一次,通过经销商批量导入又来了一次。系统里出现了三条"看起来很像"的记录。
通俗理解:就像公司花名册里,同一个员工因为入职时用了本名、后来改了英文名,结果被录入了两次。
问题二:不一致数据
销售系统里的客户地址已经更新了,但财务系统里的开票地址还是旧的。客服系统里的联系人电话是最新的,但订单系统里还在用旧电话。
通俗理解:花名册上的手机号更新了,但考勤系统、报销系统、门禁系统里的手机号还是旧的。
问题三:过时数据
供应商的银行账户三年前变更过,但系统里的付款信息没有同步更新。结果财务每次付款都打到旧账户,退回后再人工处理。
通俗理解:花名册上某个员工已经离职半年了,但因为没有人去标记"离职",他每个月还能收到系统自动发的内部邮件。
针对这三类问题,系统层面通常需要两个基础能力:重复检测和合并逻辑。
下面是一段简化版的重复检测与合并逻辑:
function detectDuplicateCustomers(candidates) {
const groups = [];
for (const c of candidates) {
const exact = groups.find(g => g.taxId && g.taxId === c.taxId);
const fuzzy = groups.find(g =>
nameSimilarity(g.name, c.name) > 0.85 &&
(g.phone === c.phone || g.email === c.email)
);
const match = exact || fuzzy;
if (match) {
match.records.push(c);
} else {
groups.push({ records: [c] });
}
}
return groups.filter(g => g.records.length > 1);
}
function mergeCustomerGroup(group) {
const base = group.records.find(r => r.status === 'approved')
|| group.records.sort((a, b) => b.updatedAt - a.updatedAt)[0];
const merged = { ...base, sourceIds: group.records.map(r => r.id) };
for (const r of group.records) {
if (r.id !== merged.id) markAsMerged(r.id, merged.id);
}
return merged;
}
这段代码的核心思想是:
- 先检测:通过精确匹配(如统一社会信用代码)和模糊匹配(名称相似度 + 辅助字段)找出可能的重复。
- 再合并:选定一条作为主记录,其他记录标记为"已合并"并指向主记录,保证历史交易不被破坏。
为什么必须是"一份数据、一个真相"
有些工程师会问:为什么不能各模块各自维护一份客户数据?销售系统管销售的客户视图,财务系统管财务的客户视图,各自独立,互不干扰。
这个想法听起来很符合"微服务自治"的理念。我们在第 30 篇文章里讨论 DDD 时,也提到过一个概念:限界上下文(Bounded Context)。不同上下文中,同一个名词的含义可以不同。例如销售上下文中的"商品"关注的是价格和库存,财务上下文中的"商品"关注的是成本科目和计价方式。
但这里有一个关键边界:限界上下文允许视角不同,但不允许身份不同。
销售上下文可以只关心客户等级和联系人,财务上下文可以只关心开票信息和信用额度。但它们必须承认"这是同一个客户"。如果销售上下文里的"华东智联"和财务上下文里的"华东智联"被系统识别为两个不同的实体,那么所有跨模块的协作都会断裂。
"一份数据、一个真相"(Single Source of Truth)不是要求所有系统用同一张表,而是要求所有系统对"谁是华东智联"这个问题给出同一个答案。
具体的实现方式可以是:
- 各系统保留自己的本地数据,但客户的"唯一身份标识"来自同一个源头。
- 各系统可以扩展自己关心的字段,但核心的身份字段(如客户编码、统一社会信用代码)不允许本地修改。
- 当身份字段需要变更时,必须通过主数据服务统一处理,并同步给所有消费方。
这才是 MDM 的真正目标:不是消除差异视角,而是保证身份一致。
总结
今天我们聊了主数据管理这个看起来"不性感"但极其重要的话题。
主数据是 ERP 的地基。客户、供应商、商品、科目、组织架构,这五类数据量不大,但影响了从采购到付款、从订单到收款、从库存到财务的每一个流程。
如果主数据治理不好,事件驱动架构只会把错误传播得更快,微服务拆分只会让不一致变得更隐蔽,DDD 的限界上下文也会因为身份无法对齐而失去协作基础。
文章的最后,我想把今天的内容压缩成三个可以带走的判断:
- 主数据的问题,首先是治理问题,然后才是技术问题。 谁来创建、谁来维护、谁来审批,这些规则比同步机制更重要。
- 同步模式没有银弹。 主从复制、发布订阅、API 网关,三种模式可以共存,关键是根据数据变化频率和一致性要求做选择。
- "一份数据、一个真相"的核心是身份一致,不是视角统一。 各模块可以有自己的业务视图,但必须承认同一个身份。
至此,从业务流、产品模块到架构设计,这个系列已经走完了完整的一圈。
下一篇文章是系列的收尾篇。我会把前面的内容串起来,聊聊从 Coder 到 Architect,一个程序员理解业务之后,会做出什么不一样的技术判断。
往期回顾
- 业财通识32:事件驱动架构——用事件解耦业务模块
- 业财通识31:DDD 战术设计——聚合根、实体与值对象
- 业财通识30:DDD 战略设计——用限界上下文划分业务边界
- 业财通识29:审批流设计——从纸质签字到电子化流转
- 业财通识28:权限设计——谁能看、谁能改、谁能批
- 业财通识27:状态机设计——用状态驱动业务流转
- 业财通识26:功能模块划分——如何把业务流程变成系统菜单
- 业财通识25:业财对账——打通业务与财务的最后一公里
- 业财通识24:银行对账——银行流水与账本的逐笔核对
- 业财通识23:应付对账——管住每一笔该付的钱
- 业财通识22:应收对账——确保每一笔钱都收回来
- 业财通识21:库存成本计价——FIFO、加权平均与标准成本
- 业财通识20:安全库存——科学补货的数学基础
- 业财通识19:可用库存——为什么账上有货却不能卖
- 业财通识18:调拨——多仓协同的物流调度
- 业财通识17:盘点——账实一致的最后防线
- 业财通识16:出库——从销售发货到领料消耗
- 业财通识15:入库——四种场景下的库存增加
- 业财通识14:应收账款——从开票到回款的风险管控
- 业财通识13:价格策略——多维定价与动态调整
- 业财通识12:一个客户,为什么会有三条记录?——CRM 作为主数据底座的三层模型
- 业财通识11:从开票到收款,企业如何收回每一分钱?
- 业财通识10:当货物发出,系统里发生了什么?
- 业财通识09:订单确认前,系统如何防止坏账风险?
- 业财通识08:企业赚钱的第一步,从"潜在客户"到"销售合同"
- 业财通识07:业财难点之"暂估入账"与冲销
- 业财通识06:什么是采购在途?它对库存预测的价值
- 业财通识05:商品世界的基石——SPU 与 SKU
- 业财通识04:万事俱备,如何优雅地"打款"给供应商?
- 业财通识03:收到供应商账单,能直接付款吗?
- 业财通识02:当货物上门,系统里发生了什么?
- 业财通识01:企业花钱的第一步,从"购物清单"到"法律合同"
关于十三Tech
资深服务端研发工程师、架构师、AI 编程实践者。
希望能和大家一起写出更优雅的代码。
如果正好有需要,可以支持一下
这里放了一个阿里云活动入口。你本来就打算了解这类服务的话,可以从这里进去;如果有推广收益,我会优先用来覆盖服务器、域名和维护成本。
