资讯动态

REA业务建模实战:用资源-事件-主体梳理订单、库存与对账难题

发布时间:2026/10/14 12:59:00 来源:尧图企业网站定制
常年跟业务系统打交道的人多少都经历过这种场面订单表里的“状态”字段从1改到8每个状态还配了一堆说明文档财务说库存金额对不上仓库说账实不符产品经理说“这个需求加个字段就行”结果一上线数据就乱了。我在某个后台系统改造项目中靠一套不算新的业务建模思路把这些问题压住了一大半。这套思路就是标题里的“rea”——我在这里说的是 Resource-Event-Agent资源-事件-主体一个从业务语义出发、再落成数据结构的方法。这篇文章不打算复述理论教材而是用一个个真实业务场景讲清楚怎么用 REA 拆解业务流程、画出数据模型以及我在实战里踩过的坑和排查经验。适合正在做业务分析、后台产品设计、财务系统建模或数据库设计的同学参考。1. REA到底在解决什么问题1.1 传统“建表思维”为什么容易翻车大多数团队拿到需求后的第一反应是建表客户表、订单表、库存表、流水表。订单表尤其容易爆炸因为业务上“下单”这个动作会牵出预占库存、锁定价格、分批发货、部分退款、售后换货、跨期结算等各种情况。于是订单表从一张干净的主表慢慢变成堆满冗余字段的“万金油表”状态枚举越加越多查询越来越慢对账逻辑越来越绕。问题不在“订单”这个概念本身而在于一开始建模时只盯着数据结构长什么样没回答业务到底发生了什么。订单表里存的不只是“订单”它还混杂着承诺、交付、开票、收付款等多类事件。把这些不同性质的信息压在一张表里返工几乎必然。1.2 REA的出发点先回答“发生了什么”REA 的思路恰好反过来。它要求建模者先把业务中的活动拆成三类基础要素资源Resource、事件Event、主体Agent再谈论表结构。所以画 REA 模型更像是在还原业务事实而不是在设计一张张关系表。1999 年我在读项目管理相关资料时第一次接触 REA 的原始框架它的根子是会计信息系统理论解决的是传统会计科目表在信息系统时代表达力不足的问题。传统借贷记账法把业务抽象成“借什么、贷什么”这对财务报表够了可对于数据系统远远不够。系统需要知道“谁在什么时候做了什么导致哪些资源发生了增减”。REA 恰恰提供了这套表达体系。1.3 什么场景适合用 REAREA 不是万能的。轻量级管理工具比如一个只有几十人用的刷题打卡小程序直接建表更高效强行套 REA 就是自找麻烦。但凡是多主体、多资源、多事件流转的业务比如进销存、采购付款、订单履约、售后维修、会员积分体系REA 都能帮你把混乱的需求理成一条清晰的线索。还有一个典型场景是财务业务一体化改造。团队里财务和开发经常因为“这个字段到底记什么”吵起来。财务关心借贷开发关心状态REA 把两边语言对齐财务上的“收入确认”对应业务上的“交付事件”“应收账款增加”对应“已交付未收款”的事实。这个对应关系一旦理清后续对账和审计都轻松很多。2. 核心概念拆解资源、事件、主体到底怎么理解2.1 资源必须能被识别和计量REA 里的资源不是指所有客观存在的东西而是指“有价值、可被交换或使用、能被系统识别和计量”的对象。现金、库存商品、厂房设备、专利许可、劳务工时、会员积分这些都能算资源。反过来“客户关系”“品牌声誉”这种难以稳定计量的东西如果系统里没有确定的口径我不建议直接建模成资源。实操中要注意资源通常不只是一个“数值”它往往自带批次、有效期、规格等维度。比如同样是库存商品一批货可能是 2024 年 3 月生产另一批是 2024 年 9 月生产保质期不同成本也可能不同。REA 本身不规定你把这些维度放哪但它要求模型保证“每一条资源变动都能追溯到具体对象”。我的习惯是给资源加一个全局编号类似“资源实例 ID”再在实例下挂业务属性。2.2 事件唯一不需要怀疑的主角如果说 REA 和传统建模有什么最关键的区别那就是事件在 REA 里是绝对的核心。事件是“对资源产生影响的一次发生”它必须具备时间、数量、相关资源、相关主体等基本信息。库存入库、销售出库、支付货款、确认收货都是事件。事件又分成两类交换类事件和转换类事件。交换类事件对应“经济交换”比如拿现金换商品系统里会同时出现资源的减少和增加转换类事件对应“生产加工”或“消耗使用”比如原材料经过加工变成成品它不一定立刻涉及外部主体但内部一定发生了资源形态的变化。我在建模时会给自己定一条规矩凡是系统中需要追溯“这笔数据是怎么来的”时一定先找事件而不是先找状态。状态只是结果的快照事件才是原因。比如“库存数量为 10”是一个状态我们更关心的是“哪些入库事件让库存变成 10哪些出库事件又让它减少”。2.3 主体不要忽略了角色和时间维度主体就是事件的参与方可以是内部员工、部门也可以是外部客户、供应商甚至可以是系统定时任务。一个比较常见的坑把“用户”单一地对应成“主体”。真实业务里同一个自然人可能既是普通顾客又是代理商还是公司内部采购员。如果主体表里只放一个人名后面算返佣、算员工折扣、区分客群时只能靠加角色字段硬撑。我的做法是主体和角色分开。主体表只管“这是谁”比如姓名、统一信用代码或身份证号维度的唯一标识角色或者参与关系单独建模比如“某主体以客户身份参与销售出库事件”“以供应商身份参与采购入库事件”。这样既不会复制主体数据也能支持同一主体多个身份的情况。2.4 三个要素如何连成一张网要素之间靠关系连接。资源与事件之间是“流入流出”关系事件发生导致某种资源增加或减少。事件与主体之间是“参与”关系某个主体发起了事件某个主体接收了事件。事件与事件之间还可能有“对应”关系比如销售出库事件和应收账款增加事件在逻辑上是成对出现的采购付款事件和采购入库事件也会互相勾稽。用一句话概括REA 模型是“资源经事件流向主体事件之间又通过经济对应关系互相绑定”的业务网络。这跟复式记账的“有借必有贷”很像但 REA 走得更远因为它保留了业务事实而不是只留下借贷数字。3. 实操把一套销售加采购流程改造成 REA 模型3.1 场景设定某贸易公司的核心业务我拿“某贸易公司”作为模拟项目 X 来讲。这家公司从供应商采购商品入库到自家仓库再销售给客户同时做库存盘点和损耗处理。业务看着简单但牵扯到的资源有货币资金、库存商品、应收账款、应付账款主体有供应商、客户、采购员、销售员、仓库管理员事件更是五花八门。第一步不是画表而是把所有业务活动列出来然后逐一判断它是不是一个 REA 事件这里有张我常用的分类表给你参考。业务活动是否事件涉及资源涉及主体事件类型采购下单是无直接资源变动形成采购承诺采购员、供应商承诺事件供应商发货是在途库存增加供应商、仓库管理员交换类事件采购入库是库存商品增加供应商、仓库管理员交换类事件向供应商付款是货币资金减少应付账款减少采购员、供应商、财务交换类事件销售下单是形成销售承诺销售员、客户承诺事件销售出库是库存商品减少应收账款增加销售员、客户、仓库管理员交换类事件收到客户付款是货币资金增加应收账款减少客户、财务交换类事件盘点调账是库存商品增减仓库管理员、盘点员转换类事件查看库存报表否不改变资源无非事件判断标准只有一个这个活动是否改变资源存量或资源流向。如果既不改变存量也不产生新的权利义务关系就不应该成为事件。3.2 从事件反推资源与主体的明细映射事件确定后再给每个事件标注资源流向和参与主体。以“销售出库”为例。这个事件至少涉及两类资源库存商品减少从仓库流向客户应收账款增加公司对客户的收款权利。它至少涉及三类主体销售员发起事件、客户接收商品、仓库管理员执行发货。如果公司还要记录物流承运方承运方也应当作为主体挂上来。再比如“收到客户付款”。这个事件涉及货币资金增加应收账款减少事件由客户发起公司财务确认银行账户可以理解为一个内部主体也可以作为资源实例处理。我见过不少团队在这纠结银行账户到底是资源还是主体。我的建议是账户本身作为“货币资金资源”的实例银行作为外部主体这样后续做资金流水和对账单都方便。这一步做完后你已经拥有了一张“事件—资源—主体”矩阵。这个矩阵特别适合跟产品经理、财务对需求因为每个事件都对应业务上的一件具体事大家不会为了表字段吵架。3.3 落地成数据库实体与关键字段矩阵确认后才进入数据库设计。核心表可以分成四类资源表、事件表、主体表、关联表。下面是一套简化但可运行的 SQL 示意覆盖核心逻辑。-- 资源表一种资源可以有一个实例表这里先简化为单一表 CREATE TABLE resource ( id BIGINT PRIMARY KEY AUTO_INCREMENT, resource_code VARCHAR(64) NOT NULL UNIQUE, resource_name VARCHAR(128) NOT NULL, unit VARCHAR(32) NOT NULL, category VARCHAR(64), current_quantity DECIMAL(18,4) NOT NULL DEFAULT 0, valid_from DATETIME, valid_to DATETIME, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 事件表记录每一次业务发生 CREATE TABLE event ( id BIGINT PRIMARY KEY AUTO_INCREMENT, event_no VARCHAR(64) NOT NULL UNIQUE, event_type VARCHAR(64) NOT NULL, event_time DATETIME NOT NULL, location VARCHAR(128), remark VARCHAR(512), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 主体表 CREATE TABLE agent ( id BIGINT PRIMARY KEY AUTO_INCREMENT, agent_code VARCHAR(64) NOT NULL UNIQUE, agent_name VARCHAR(128) NOT NULL, agent_type VARCHAR(32) NOT NULL, tax_no VARCHAR(64), valid_from DATETIME, valid_to DATETIME, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 资源与事件的流入流出关系 CREATE TABLE event_resource ( id BIGINT PRIMARY KEY AUTO_INCREMENT, event_id BIGINT NOT NULL, resource_id BIGINT NOT NULL, flow_direction VARCHAR(8) NOT NULL, -- IN 流入资源OUT 流出资源 quantity DECIMAL(18,4) NOT NULL, amount DECIMAL(18,4), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 事件与主体的参与关系 CREATE TABLE event_agent ( id BIGINT PRIMARY KEY AUTO_INCREMENT, event_id BIGINT NOT NULL, agent_id BIGINT NOT NULL, role VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 事件与事件的对应关系用于表达经济交换 CREATE TABLE event_correspondence ( id BIGINT PRIMARY KEY AUTO_INCREMENT, increment_event_id BIGINT NOT NULL, decrement_event_id BIGINT NOT NULL, correspondence_type VARCHAR(32) NOT NULL, amount DECIMAL(18,4), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );这套结构最大的好处是业务加需求时不需要给一张主表疯狂加列。比如“客户下了订单但还没付款”系统里自然存在一条销售承诺或销售出库事件没有对应的付款事件这就是应收账款等收款事件出现再用 event_correspondence 把两者挂上账目自然平了。3.4 一个具体实例卖出一批货的全过程我们走一遍“卖出 10 件商品给客户”的完整数据流程。第一个事件是“销售出库”。在 event 表插入一条记录event_type 为“SALES_DELIVERY”event_time 为发货时间。同时在 event_resource 表插入两条记录一条是库存商品资源 ID 的 OUT 方向数量为 10另一条是应收账款资源 ID 的 IN 方向金额为 1000 元。在 event_agent 表插入销售员、客户、仓库管理员三条参与记录。第二个事件是“收到货款”。再插入一条事件event_type 为“CUSTOMER_PAYMENT”同时在 event_resource 表插入一条记录货币资金资源 ID 的 IN 方向数量为 1000 元应收账款资源 ID 的 OUT 方向数量为 1000 元。最后用 event_correspondence 把两个事件关联起来correspondence_type 为“PAYMENT_FOR_DELIVERY”。这样处理之后查询“这笔订单是否回款”就变得非常直观找到销售出库事件看有没有对应关系的收款事件。如果状态不对一定是事件没录全要么漏建了收款事件要么对应关系没建立。有的同学会问那现有的“订单表”怎么办我不建议立刻删掉订单表。订单表可以作为“业务单据入口”或“聚合展示层”保留但它背后的数据来源应该逐步替换成事件表和关联表。换句话说让订单表从“多方事实的存储容器”变成“多个事件结果的实时视图”。4. 常见问题与排查技巧实录4.1 把事件当状态把状态当事件这是新手最容易踩的坑。比如有人把“出库完成”设计成一个字段每次流程走完就更新字段。表面上看也能运作可一旦需要追溯“上次出库具体是哪些批次、经手人是谁、当时库存是多少”字段式方案就完全不够用。我的排查方法是拿到一个需求先问三个问题。第一它是一次发生的事实还是一个截止当前的结果发生过的事实就是事件结果就是状态。第二这个字段会不会在同一对象身上反复变化如果会很可能是事件被压缩成字段了。第三如果业务要回溯 90 天内的每一次变化这个字段方案能回答吗如果发现不对就把相应的“状态位”拆回事件表。订单状态里的“已发货”就可以拆成“发货事件已存在”“已完成”就可以拆成“发货事件和收款事件均已存在”。状态依旧可以保留为冗余字段方便快速查询但备份真相必须来自事件。4.2 资源增减方向搞反对账怎么都不平我在一次模拟项目中把“采购付款”的 resource_out 方向写错了导致应付账款越对越多。排查下来发现是建模时误把“付款”理解成“供应商资源流入”而不是“货币资金流出、负债减少”。这里有一个消除歧义的技巧每建立一个事件资源关系不要只写“数量”和“金额”一定要在业务层面说清“谁的什么资源发生了什么变化”。比如采购付款事件站在公司角度是“银行存款减少”和“应付账款减少”站在供应商角度是“货币资金增加”和“应收账款减少”。REA 模型允许从不同主体视角建模但一套系统里必须绑定同一个视角推荐统一站在公司或平台运营方视角。4.3 过度建模把查看、操作记录都当事件刚上手 REA 时很容易走向另一个极端所有动作都建事件连“用户点击了一下查询”也建事件。这是过度建模。真正的业务事件应当具备经济后果要么改变了资源要么形成了权利义务。纯粹的操作痕迹可以由日志系统负责不需要混入核心业务模型。如果团队判断一个动作是否要建模成事件我提供一个简单规则这个动作如果相隔一年后再看还需要知道它的具体内容和影响吗如果不需要日志就够了如果需要进入 REA。比如“仓库管理员今天打扫了仓库”不需要建模“仓库管理员盘点后确认库存少了 3 件”必须建模。4.4 主体身份时变员工离职、客户改名的处理主体表如果不考虑时间维度早晚出问题。一个客户 2023 年叫“张三贸易”2025 年更名成“张三供应链”如果主体表直接更新名称历史销售事件里“卖给谁了”就跟着被改掉审计时说不清。解决办法很简单主体表加 valid_from 和 valid_to或者把主体与身份信息拆开。历史事件关联主体 ID不做覆盖式更新。名称变更时新建一条主体记录并在标识字段上维护同一个全局唯一编码。销售出库事件始终指向那个唯一编码展示层可选显示当前名称或历史名称。这样既保证了身份的连续性也保留了历史事实。4.5 排查脚本和快速自查清单针对几个高频问题我准备过一个自查 SQL 思路供你参考。第一个常见问题是“资源存量与事件流水不一致”。排查时先汇总所有 event_resource 里某个资源 ID 的 IN 和 OUT 数量再对比 resource 表的 current_quantity就能定位是事件录入缺失还是资源初始值不对。第二个问题比较隐蔽“某些事件找不到对应关联”。用一段简单查询找出所有 increment_event 在 event_correspondence 里没有配对的记录。特别是应收账款和库存类事件业务上应该成对出现。结合这些排查经验我给自己总结了一份每次建模都要过的清单提示第一每个资源变动是否都能向上追溯到某个事件第二每个事件是否都记录了至少一个参与主体第三交换类事件是否建立了事件间对应关系第四是否存在被当成字段的事件第五主体名变更是否会影响历史记录这五条里任何一条答不上来模型都要先补全再开发不要指望后期靠补数据兜底。5. 我的习惯先画 REA 草图再谈其他最后分享一个我个人的操作习惯。接到一个新业务模块时我基本不动手建表而是找一块白板把资源、事件、主体用三列写下来。先列出所有看得到的“东西”再列出所有线上线下“动作”最后在旁边标注“谁发起、谁接收”。把这些列齐后再把动作按“换东西”和“变东西”分组。这时候数据需求已经自己浮出来了。有一次某团队老板级别的人看我在白板上画这些说“这不就是流程图吗”。我觉得很像但不完全是。流程图表达的是操作顺序REA 表达的是业务事实与事实之间的勾稽关系。它更像物理学家画受力分析图资源是对象事件是力主体是施力点。受力图画清楚了运动轨迹自然就判断出来了。这套方法在一个后台系统改造中带来的变化很明显最大的变化是“返工”少了。以前改需求要动四五张表现在大多只是新增一种事件类型或调整事件与资源的关联。产品经理和开发也找到了共同语言不再各说各话。后续我还想把 REA 跟微服务划分、领域事件设计结合起来聊一聊这又是一个能展开的话题。希望这篇实战向的梳理对正在跟业务模型缠斗的你有些许帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑