news 2026/10/19 14:56:00

REA建模实战:资源事件代理模型如何搭稳进销存与对账体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
REA建模实战:资源事件代理模型如何搭稳进销存与对账体系

如果你在系统设计笔记里看到“REA”三个字母,很可能是在某个进销存、财务或供应链相关的讨论里。我最近恰好帮一个传统制造业客户梳理数据模型,业务方被月底对账折磨得不轻:采购系统、库存系统、销售系统各有一套台账,合并之后账实不符,财务只能逐笔翻凭证。后来我们终于把争论焦点收拢成三个英文词:Resource、Event、Agent,也就是 REA。这篇内容不是概念复读,而是把我实际建模时的判断依据、表结构、踩过的坑一次讲清楚,适合正在设计业务系统或做领域建模的开发者、产品经理和业务分析人员。

1. 从一套让财务抓狂的借贷分录说起

1.1 借贷记账为什么在业务单据上显得别扭

传统进销存系统里,底层数据大多是按“借/贷”思想设计的。我之前遇到的一个库存模块,每次采购入库,系统先生成一张“借库存商品、贷应付账款”的分录。仓管看“入库单”觉得货物多了,财务看“分录”觉得应付多了,同一个业务事实被拆成两套语言。等到月底核对,入库单数量是5,但分录金额用的是加权平均单价,和当前采购单价对不上,问题就来了。

业务量小的时候,手动调账还能忍。一旦SKU上千、供应商几十家、退货换货频繁,这种“业务动作→会计科目”的转换链会变得极长。每个环节的换算规则都可能被业务方临时改写,模型漂移几乎不可避免。我后来也想过直接优化会计分录,但发现解决不了根本问题:我们缺的不是更好的科目体系,而是一个更贴近业务事实的中间模型。这是我转向REA模型的核心原因。

1.2 REA的基本三分法:资源、事件、代理

REA模型的大思路其实不复杂。它认为,任何一个业务系统都可以围绕三个关键词来建模:

  • 资源:会被业务事件增加或减少的东西,例如库存商品、原材料、现金。
  • 事件:业务发生的瞬间动作,例如采购入库、销售出库、付款。
  • 代理:参与并通过事件影响资源的个人或组织,例如供应商、客户、仓库管理员。

三分法的最大价值,是让模型跟着“业务发生了什么”走,而不是跟着“账上该怎么写”走。库管说“入库”,REA里就是一个事件;这个事件关联某个资源(物料批次),同时关联两个代理(提供方供应商、接收方仓管)。后续财务无论怎么定义科目,这个原子事实都不会被破坏。

1.3 为什么要把事件放在模型中心

传统模型通常以“单据”或“对象”为中心,REA偏偏把事件放在中心位置。实际原因是:单据是人对事实的包装,可能合并、可能拆分、可能事后补号;而事件是业务世界里真实发生的一个动作,一旦发生就不能消失。以事件为中心,后续对账、审计、分析都围绕事实展开。

我在项目里经常提醒团队:“先别急着设计表,把业务发生过的所有动作列出来,给每个动作标上它改变了哪个资源、由哪些代理参与,模型就已经出来一半了。”这句话听起来简单,实操中能避免大量返工。

2. 资源、事件、代理的边界判断:该建模到什么程度

2.1 资源的边界:能不能被事件增减

最常犯的错是把“任意业务实体”都当成资源。判断标准其实很朴素:只有当这个对象会成为某类经济事件的增量或减量时,它才是一个REA资源。我在各种需求评审中总结过一组常见候选对象:

| 候选对象 | 是否作为资源 | 判断理由 | | 库存商品 | 是 | 采购入库增加它,销售出库减少它 | | 仓库 | 否 | 仓库本身不被事件“增减”,除非专门做容量管理 | | 供应商 | 否 | 供应商是代理,不是资源 | | 库存批次 | 是 | 批次库存会被入库、出库、报废事件增减 | | 合同 | 否 | 合同是承诺载体,不是被增减的对象 |

有一种例外值得注意:如果你专门做仓储容量管理,关心“可用库位”的增减,那么库位容量可以建模为资源。我见过一个模拟项目把储位当资源,处理空箱回库场景时非常顺手。这说明资源判断要结合业务事实,而不是套规格。

2.2 事件的粒度:按行拆分还是按单合并

事件粒度直接决定模型粗细。一张入库单可能包含多个物料、进入多个库位,如果整张单作为一个事件,后续想查“某批次在一号库位还剩多少”就无从下手。我建议按业务中最小的对账单元来拆事件。

判断事件粒度是否合理,可以问四个问题:

  1. 这个动作发生在什么时间?
  2. 它影响的是哪个资源实例(哪个批次、哪个库位)?
  3. 谁提供、谁接收、谁负责?
  4. 只保留这一条记录,能否完整回答上面三个问题?

四个问题都能回答,粒度就是合适的。同一张单据可以按物料、库位拆成若干事件,同时保留单据号作为聚合字段,供界面层拼装成一整张单据视图。这样做的好处是向上可以聚合,向下可以追溯,模型不会因为粒度过粗而失去灵活性。

2.3 代理的角色:内部代理和外部代理都要留下

每个事件通常会涉及两类代理:外部代理(供应商、客户)和内部代理(采购员、仓管、业务员)。REA模型中,事件与代理的关系要成对出现:谁提供资源,谁接收资源,都必须记录。实际项目中最容易遗漏的是内部代理,很多人觉得“仓管是谁不重要”,等到盘点差异需要追责时,才发现缺少责任人信息。

代理也不应该无脑建到“员工个人信息”的粒度。只有当需要按具体责任人追责时,才把“人”挂到事件上;如果只是做供应商账龄分析,把外部代理的组织级信息挂到事件上就足够了。这个尺度拿捏,比建模型本身更考验经验。

3. 一个完整业务链的REA建模拆解:采购入库到销售出库

3.1 设定一个典型场景

假设某公司做零部件进销存,核心流程大致如下:

  1. 采购员向供应商下采购订单,供应商发货;
  2. 仓管收货、检验、入库,形成库存;
  3. 车间领料加工成半成品,完工后入半成品库;
  4. 业务员接客户订单,仓管按订单出库发货;
  5. 客户收货后付款,公司收到货款。

业务痛点概括成一句话:月底账实不一致,说不清某个批次物料到底消耗到了哪里。

3.2 第一步:先列事件,再补资源和代理

照我的做法,先不看原来的库表和字段,只用“事件”统一描述所有业务动作。列出主要事件后,再给每个事件补充资源和代理:

| 事件 | 受影响资源 | 资源方向 | 内部代理 | 外部代理 | | 采购入库 | 原材料批次 | 增加 | 仓管(接收) | 供应商(提供) | | 生产领料 | 原材料批次 | 减少 | 车间(接收) | 无 | | 完工入库 | 半成品/产成品 | 增加 | 车间(提供)、仓管(接收) | 无 | | 销售出库 | 半成品/产成品 | 减少 | 仓管(提供) | 客户(接收) | | 付款 | 货币资金 | 减少 | 财务(提供) | 供应商(接收) | | 收款 | 货币资金 | 增加 | 财务(接收) | 客户(提供) |

这张表写作时非常简单,但它是整个系统的骨架。我还会额外加一列“原始单据号”,方便后续把事件和采购单、销售单、收付款回执一一对应。

3.3 第二步:补齐承诺,保证经济交换闭环

仅仅列出事件还不够。一个完整的REA模型要求:经济交换中必须同时存在“给出资源”和“收到资源”两个镜像事件,否则经济链条是断的。比如采购:供应商发货是资源流入,我方付款是资源流出,中间还应该有“应付承诺”把订单和后续付款关联起来。

我当时的落地办法是:在事件之外增加“承诺记录”(订单、合同),并将其与后续经济事件关联。这样账龄分析、供应商对账都能直接引用承诺数据,不用靠人工猜测。库存模块的账实核对也因此简化了:每一条库存余额变化,都能追溯到事件以及事件对应的原始单据。

3.4 第三步:把模型翻译成听懂的人话

最后一步,把三元组关系翻译成业务语言。假设一批钢材3月1日入库100件,3月5日出库30件,用REA表达就是:

  • 采购入库事件E1:资源R1增加100件,供应商A1提供,仓管A2接收。
  • 销售出库事件E2:资源R1减少30件,仓管A3提供,客户A4接收。
  • 累计剩余库存:100 - 30 = 70件。

把这张表放在项目评审会上,业务方只要扫一眼就能理解。最直观的效果是:双方不再因为“科目名称”争来争去,而是直接看着事件流确认业务事实。这是REA带给我的最大红利。

4. 落地设计与查询:把REA变成数据库里的表

4.1 用只增事件表替代库存余额表

传统方案很喜欢设计一张“库存余额表”,每次出入库直接更新余额,查询时读余额,简单直接。但余额表是状态快照,一旦写错,你很难知道它为什么错。REA落地则不同:只追加不可变的事件记录,余量通过事件计算得出。

最小表结构分成四块:资源表、代理表、事件表、关联表。关联表负责把事件与资源、事件与代理挂起来,形成多对多关系。

4.2 最小建表SQL示例

我用结构相对清晰的PostgreSQL语法做个示例,方便直接参考:

create table rea_resource ( id bigserial primary key, resource_type varchar(64) not null, name varchar(255) not null, unit varchar(32) ); create table rea_agent ( id bigserial primary key, agent_type varchar(64) not null, name varchar(255) not null ); create table rea_event ( id bigserial primary key, event_type varchar(64) not null, occurred_at timestamp not null, source_doc_no varchar(64) ); create table rea_event_resource ( event_id bigint not null references rea_event(id), resource_id bigint not null references rea_resource(id), direction smallint not null check (direction in (1, -1)), quantity numeric(18, 4) not null, primary key (event_id, resource_id) ); create table rea_event_agent ( event_id bigint not null references rea_event(id), agent_id bigint not null references rea_agent(id), role varchar(64) not null, primary key (event_id, agent_id) );

这里 direction 用 1 表示资源增加,-1 表示资源减少,quantity 永远填正数。查询当前库存时,直接对direction * quantity做求和即可。

如果一个事件会对同一个资源实例产生多条不同的增减记录,比如同一批原料在同一事件里既有报废又有转库,可以给关联表加一个 seq 序号字段,避免主键冲突。

4.3 库存余量计算与逐笔追溯查询

查询当前库存余量,用一段带分组的SQL就能搞定:

select r.id, r.name, sum(er.quantity * er.direction) as balance_quantity from rea_event_resource er join rea_resource r on r.id = er.resource_id group by r.id, r.name having sum(er.quantity * er.direction) <> 0;

如果想针对某个批次做逐笔追溯,把事件按时间排序重放即可:

select e.id, e.event_type, e.occurred_at, er.direction, er.quantity from rea_event e join rea_event_resource er on e.id = er.event_id where er.resource_id = 1001 order by e.occurred_at, e.id;

生产环境数据量大时,全表聚合会变慢。我建议定期用物化视图或批处理任务生成库存快照,同时保留事件明细供追溯。查询层读快照,审计层读事件明细,两者互不干扰。

4.4 不可变事件带来的审计优势

事件不可变,意味着任何库存异常都能被精确复盘。某批次库存出现负数,逐条回放事件流,立刻就能定位是入库漏录还是出库多录。对账时,报表数据可以基于事件流重新计算,不需要担心历史被覆盖。

我在实际项目中尤其喜欢“单据号”字段,它把事件和原始业务单据连接起来。财务问“这批货怎么少了”,我们不再回答“可能是上月调账调的”,而是直接给出事件意图链:采购入库→生产领料→销售出库,每一步都有据可查。

5. 我见过的三个典型误用,以及对应自查方法

5.1 把一切对象都当成资源,模型迅速膨胀

有些同学学完REA后,什么表都往资源里放:客户建资源、合同建资源、仓库建资源。结果模型越来越大,查询逻辑复杂到没人敢改。

判断标准仍然是那条:这个对象会被事件增加或减少吗?客户不会因为销售事件变少一个,合同不会因为付款事件变多一份,所以它们都不是资源。资源必须是经济事件直接作用的载体。

5.2 事件粒度拍脑袋,一张单据等于一个事件

把整张入库单当一个事件的做法,短期看数据量小没毛病,长期会卡在对账上。一旦要查“某个批次某天在某库位的结存”,事件粒度太粗就查不出来,只能重新改历史数据。

我会坚持按最小对账单元去拆事件,即使最初多出一些记录。事件拆细了,后续聚合视图可以做得和过去完全一样;但如果一开始拆粗了,再想拆细就得翻工。

5.3 只建模了交换,没建模承诺

还有个常见问题是只记录“入库”和“付款”,忽略中间的订单承诺。结果就是应付款对账时,财务要另外维护一张Excel台账,因为系统里找不到“这笔应付对应哪个订单”。

补齐承诺记录后,整个链路变成:订单承诺 → 采购入库 → 付款结算。这三个环节串起来,账龄分析、供应商对账都是现成的数据,根本不用额外加工。

5.4 每次建模前过一遍自查清单

我现在做REA建模前,都会带着业务方过一张自查清单:

| 自查问题 | 通过标准 | | 这个对象会被事件增减吗 | 只有“会”才建模为资源 | | 事件能否回答时间、资源、代理、方向 | 完整回答才算粒度合适 | | 代理是否成对记录 | 不能只有提供方或接收方 | | 资源增减是否大致守恒 | 加总所有增减应与盘点数量对得上 | | 每个事件能否追溯到原始单据号 | 每行都有 source_doc_no |

这套清单帮我拦下了不少“看起来很对但落地就会炸”的设计。如果你也在做类似的建模,建议把它贴到需求文档首页。

6. REA与事件溯源、领域驱动设计放在一起看

6.1 事件溯源:状态不是第一性,过程才是

事件溯源的核心是“用事件日志重建状态”,REA几乎天然符合这种思想。REA的事件表本身就可以当作事实日志,资源余量是事件流投射出来的状态。区别只在于:事件溯源偏基础设施层,关注如何重放;REA偏业务建模层,关注事件如何分类和关联。

如果你已经用事件溯源做系统,把REA概念套上去几乎无缝。资源对应聚合里的状态对象,事件对应领域事件,代理对应参与者。

6.2 领域驱动设计:REA是一种高效的分析模式

做领域驱动设计时,大家经常纠结聚合边界怎么划。REA能提供一套现成的分析骨架:订单聚合可以映射为一个承诺事件,履约过程映射为经济事件,资源是聚合内需要保护的不变量。

我开工作坊时,会让团队先用REA画出事件流和代理关系,再套DDD的战术建模。画完之后,哪些对象该进哪个聚合、哪个事件该作为聚合根,通常比硬想清晰得多。REA和DDD并不冲突,反而像一个前置过滤器,帮团队先统一业务语言。

6.3 我什么时候选用REA,什么时候明确不用

| 系统类型 | 是否建议用REA | 原因 | | 进销存、财务、供应链 | 建议 | 强对账、强审计,事件流能支撑复杂追溯 | | 简单内容后台 | 不建议 | 普通CRUD足够,引入REA会增加大量成本 | | 高并发交易系统 | 看情况 | 建模结构可用,但查询层要配合快照设计 | | 数据仓库贴源层 | 建议参考 | 事件流贴近原始终端事实,比直接挂汇总表灵活 |

没必要让所有模块都REA。我见过一种务实的混合做法:核心库存与资金模块用REA建模,订单展示层、用户界面查询仍用普通读模型或物化视图。这样既保住审计和追溯能力,又不至于把每个页面都拖慢。

最后说一个体感很强的事:REA不是给你多一张表,而是给你一个“必须先讨论清楚业务动作”的约束。我在实际项目里遇到争议时,通常掏出一张白纸,让所有业务方把动作写下来,标上资源变化和两方代理。往往写不到一半,争论就自然消失了。如果你正要改造一个对账混乱的系统,可以先用Excel模拟一周的事件流,把每个资源增减汇总算一遍,确认守恒后再写建表语句。模型复不复杂不重要,重要的是它能替你说话。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:55:50

技术Diagram设计指南:从信息梳理到视觉规范的完整方法

做技术这些年&#xff0c;我画过不少Diagram。准确说&#xff0c;从最早的架构草图&#xff0c;到给团队讲方案时用的流程图&#xff0c;再到写文档时补的状态图、时序图&#xff0c;几乎每个环节都绕不开它。diagram-design这个活儿&#xff0c;听起来不像写代码那么硬核&…

作者头像 李华
网站建设 2026/10/19 14:55:15

用代码做工程级图表设计:架构图、时序图与流程图的落地实践

做后端开发的第三年&#xff0c;我终于受不了文档里那堆东拼西凑的破图了。架构说明里一张时序图画得歪歪扭扭&#xff0c;业务逻辑改了两次&#xff0c;图却忘了同步&#xff0c;新同事接手时对着旧图猜逻辑&#xff0c;浪费了整整一个下午。后来我把“画图”这件事认真当做一…

作者头像 李华
网站建设 2026/10/19 14:54:32

千笔降AIGC助手:论文查重与AI痕迹双降的完整实战指南

每年四月到六月&#xff0c;社交平台上最热闹的话题之一就是论文查重。随便刷一刷&#xff0c;就能看到几十条“重复率又超了”“导师说AIGC疑似比例太高”的求助。评论区给的建议来来回回就那几句&#xff1a;改被动句、换同义词、删连接词。这些土办法在十年前也许有效&#…

作者头像 李华
网站建设 2026/10/19 14:54:32

SpringBoot+Vue选课系统全解析:数据库设计、冲突处理与答辩指南

选课系统这类项目&#xff0c;几乎每隔一段时间就会在技术社区里冒出来一批。原因很简单&#xff1a;它角色划分明确、业务规则有深度、功能边界清晰&#xff0c;是少有的能把前后端分离、权限控制、事务处理、数据库设计全部串起来的“教学型”项目。我见过太多人拿到一套Spri…

作者头像 李华
网站建设 2026/10/19 14:53:15

从经营诊断到数据底座:破解专精特新企业增长与利润难题

1. 增长乏力和利润稀释&#xff0c;其实是同一个病的两种症状上周一位做精密零部件的A同学来我办公室聊了近三个小时&#xff0c;他公司刚刚通过专精特新认定&#xff0c;按理说应该松口气了&#xff0c;但他说了句话让我印象很深&#xff1a;“营收涨了18%&#xff0c;净利润反…

作者头像 李华