首页 / 资讯中心 / 文章详情

REA模型教程:用资源-事件-参与者设计可追溯的进销存与财务系统

REA模型教程:用资源-事件-参与者设计可追溯的进销存与财务系统 ★ FEATURED ARTICLE
1. 为什么是“REA”先把这个模型说清楚“REA”这三个字母摆在眼前大多数人第一反应是某个框架缩写或者是某款工具的代号。其实它不是新技术也不是某个依赖库而是一套经典的企业业务建模视角Resource-Event-Agent也就是“资源-事件-参与者”。它最早被引入会计信息系统领域目的是弥补传统借贷记账法在描述业务过程上的不足。这两年不少团队在设计订单中心、进销存、财务对账这类系统时又主动把 REA 拿回桌面把它当画表结构之前的“思维底稿”来用。我最早接触 REA 时正遇到一个挺头疼的需求公司要把库存系统和财务系统打通要求任何一笔库存变动都能追溯“谁、什么时候、因为什么单据、影响了哪些商品”。当时传统做法是订单表记金额、库存表记数量、财务账记借贷科目三个系统各记各的数据对不上时只能靠对账脚本硬碰。后来就是翻到 REA 的资料才开始意识到问题不在于某个表设计错了而在于我们把“业务是怎么发生的”给丢了。说到适合人群如果你是做后端开发、数据建模、产品设计或者正在折腾进销存、供应链、财务相关系统REA 这套视角值得花半小时认真看一遍。它不绑定语言也不绑定数据库是一套纯概念层面的思考方式。学完之后你大概率会发现很多“需求改动爆雷”的问题其实在画第一张表之前就能避免。2. 核心构件拆解资源、事件、参与者REA 把任何业务日常都拆成三类东西资源、事件、参与者。听起来简单但真正用对并不容易尤其是“事件”这一点最容易和人直觉里的“单据状态”混淆。2.1 资源Resource能被衡量和控制的东西资源在 REA 里的定义并不复杂能被组织控制、并且对业务有价值的东西。比如库存里的商品、供应商要付的款项、客户欠的应收款、仓库里的库位、公司账上的现金这些都是资源。关键判断标准有两个。第一个是“可计量”比如数量、金额、重量能用一个数值表达。第二个是“可归属”能明确说出这是谁的、在哪个环节控制着。如果一样东西无法被量化或者说不清楚归属那它多半不是资源而是某种阶段性的业务状态。常见误区是把“库存数量”这个数字当作资源。本质上库存数量是一个统计值是多个业务事件发生之后的加总结果。真正的资源应该是“某一批次的商品”“某一件可识别的货品”哪怕它在系统里只是一个小类目编号背后也代表着一组可被事件影响的对象。2.2 事件Event不可再拆的业务动作事件是 REA 里最核心、也最容易被理解偏的概念。一句话凡是可以让资源数量或状态发生变化的活动就是一个事件。比如“销售出库”“采购入库”“收到客户付款”“给供应商付款”“退货”“盘点调整”这些都是事件。事件有四个明显特征第一它在时间上是一个点不是一段持续过程第二它直接关联一个或多个资源第三它必然有参与者和责任方第四一旦发生就不可被更新只能被纠正。举个例子“销售订单”不是事件订单只是一份契约文本“订单发货”才是事件因为那一刻库存真的减少了。很多人一开始都会在这个点上传不过来我的系统里明明有“订单状态”字段从待支付到已发货再到已完成这不就是过程吗实际上这是两码事。订单状态是流程状态是业务在推进时的临时标记而事件是状态变化那一刻的业务事实。REA 要求你把业务事实单独记录不要只更新一个状态字段就完事。2.3 参与者Agent执行者与责任方第三类构件是参与者指参与到事件里的个人或组织。比如客户、供应商、销售员、仓管员、审批人甚至定时任务这样的系统角色都可以作为参与者。参与者有一个重要的双向性质一笔业务事件通常涉及两个方向的人。一个是组织内部的执行者另一个是组织外部的交易对手。比如“销售出库”事件里内部参与者是仓管员外部参与者是客户在“采购入库”事件里内部是收货员外部是供应商。这种成对出现的参与者关系让企业可以随时回答“东西从哪里来、经过谁的手、到哪里去”的问题。我通常习惯把参与者分成“发起方”和“承接方”来看内部角色和外部角色尽量分开建模。这样做的好处很明显一个客户可能同时是你的销售对象和退货对象如果只是在订单表里放一个“客户ID”字段后续要查“这个客户的所有交易往来”时就会很吃力而 REA 模型天然把这种关系摊开了。3. 为什么不直接按“借贷记账”设计原理和取舍很多人会问既然金融系统、财务系统这么多年都用借贷记账直接用科目、凭证、分录不就好了吗为什么要多绕一层 REA3.1 借贷记账法只记了“结果”没记“业务”借贷记账法的核心是复式记账每一笔金额同时记入借方和贷方从而保证账户恒等式平衡。这套方法非常成熟但它解决的是“账是否平”的问题不解决“业务到底发生了什么”的问题。举一个简单的例子一笔 500 元的销售收款在借贷记账法里记录为“借银行存款 500贷主营业务收入 500”。这笔记录能说明收到 500 元、收入增加 500 元但它没有记录是哪个客户付的款、对应哪一张销售单、卖的是哪几件商品、是在哪个门店发生的。当然传统凭证可以附说明甚至可以查关联单据但这些都是附加信息而不是记账结构本身强制承载的。这就是借贷记账法的核心短板它的数据结构围绕“科目余额”展开天然不关心业务过程。等你想做经营分析时会发现要还原“一笔销售怎么发生的”特别费力。3.2 REA 用时间和业务链还原全过程REA 的思路恰恰相反。它先记事件再记资源变化通过事件把资源和参与者连在一起。还是那笔 500 元的销售REA 模型会有一个“销售事件”记录卖出了哪些商品、数量多少、哪个客户经手关联一个“收款事件”记录收到了 500 元现金。资源和资源之间、事件和事件之间自然形成了一条完整的业务链。顺便说一个容易被忽略的好处因为每个事件都有时间戳所以 REA 天然支持“按任何时间范围统计快照”。你不需要额外做流水表只要把事件按时间过滤、按资源聚合就能算出任意时间段内的库存、收入、应收应付。这个特性在做报表系统时极其舒服。3.3 REA 给系统演进带来了什么REA 模型的另一个优势是“面向变化抗造”。业务规则一定会变今天业务流程是“销售→发货→收款”明天可能变成“预售→收款→备货→发货→确认收货”传统订单表加状态字段的方式会越加越乱。而 REA 里这是新增加几个事件类型和事件关联的事不需要重构已有表结构也不会影响已经发生的历史事实。比如系统后来增加了“分期收款”功能在传统模型里可能要在订单表里加一个“分期次数”字段同时改填几套对账逻辑在 REA 里只需要新增一个“收款计划事件”或“每期收款事件”原来的销售事件和收款事件原样不动。这种演进成本差异实际用过之后感受会很深。4. 实操走一遍从业务场景到完整 REA 模型光讲概念容易飘这一节我用一个最常见的“采购与销售”场景把建模全过程走一遍。场景设定是一家小型日用品贸易公司业务包括向供应商采购、入库、向客户销售、收款、退货。4.1 先锁定一个足够简单的业务复杂系统建模之前一定要先给场景划边界。我建议选择最常发生的核心业务闭环比如“采购入库→销售出库→收款”不要一开始就把营销活动、会员积分全部塞进来。简单场景往往能把建模逻辑练明白。等核心回路成立再往里面加活动、加策略每一次加法都会变得很自然。4.2 五步建模实操第一步把所有业务对象列出来。直接枚举你在真实业务里听到的词商品、供应商、客户、采购单、销售单、入库单、出库单、付款单、收款单、库存数量、仓库、销售员、采购员、退货单、盘点单等等。先不用判断把名录弄全。第二步区分哪些是资源。原则就是“可计量、可控制、有价值”。在这个例子里商品是资源现金是资源库存仓位的占用也是资源。而采购单、销售单这些单据不是资源它们是记录业务的载体。库存数量不是资源它是资源加总后的结果。第三步识别事件。事件一定是某一个瞬间发生的动作采购入库、销售出库、收款、付款、退货、盘点差异调整这些都是事件。单据本身不是事件单据提交的那一刻产生的“入库动作”才是事件。这一步的关键是不要用“状态转移”代替“业务发生”。第四步识别每个事件的参与者。以“销售出库”为例内部参与者是货员外部参与者是客户“采购入库”对应收货员和供应商“收款”对应财务和客户。如果没有外部参与者就要警惕是漏了角色还是这个事件其实是内部调整。第五步把资源、事件、参与者连接起来形成模型草图。资源要放到合适位置事件作为中心节点参与者放在两端。连完之后应看到一幅可以讲述整个业务流程的网状图而不是一张零散的表清单。4.3 模型完整性检查表建模完成后用下面几条标准自查能堵住大部分低级问题检查项说明每个资源是否既有“增加事件”也有“减少事件”商品有入库事件也应该有出库事件现金有收款事件也应该有付款事件每个事件是否至少连接一个内部参与者和一个外部参与者缺外部参与者的“销售”事件会丢失客户维度事件之间是否有因果关系收款事件应该能指回销售事件退货事件应该能指回原销售事件有没有把“单据编号”“状态字段”混进事件表单据辅助信息可以放在事件扩展表但不应污染事件的核心定义同一个业务动作是否重复建模如果“销售出库”和“发货”指的其实是同一时刻的同一动作只保留一次5. 把 REA 模型翻译成表结构和代码概念模型最终还是要落到数据表。REA 的优势在于一旦建模明确表结构会变得非常稳定。5.1 核心表设计思路按照 REA 模型一般需要三类基础表资源表、事件表、参与者表另外还有关联表。资源表和参与者表都比较常规重点看事件表。事件表可以设计成相对统一的通用结构CREATE TABLE event ( event_id BIGINT PRIMARY KEY, event_type VARCHAR(50) NOT NULL, occurred_at TIMESTAMP NOT NULL, operator_agent_id BIGINT NOT NULL, counterpart_agent_id BIGINT, remark VARCHAR(500) ); CREATE TABLE event_line ( line_id BIGINT PRIMARY KEY, event_id BIGINT NOT NULL, resource_id BIGINT NOT NULL, delta_qty DECIMAL(18,4) NOT NULL, delta_amount DECIMAL(18,2) );这里的思路是事件表记录“谁在什么时间做了什么动作”事件明细表记录“这个动作影响了哪些资源变化量是多少”。注意明细表里的字段是“变化量”而不是“当前余额”。这是 REA 设计里很重要的一点事件表只保存增量事实任何余额、库存数量、应收金额都应该是查询时临时聚合算出来的不要物理存储。5.2 关键代码层面的落地建议按事件表结构落到服务层时有一个强烈建议把事件设计成“只追加”数据没有更新和删除。业务发生误操作需要纠正时另外记录一个“纠错事件”而不是直接修改原事件。这在财务和库存场景尤其重要。另外由于事件表要支撑聚合查询建议在设计时就为 event_type、occurred_at、operator_agent_id 这些高频过滤字段建立索引。事件数据一旦量级上来时间范围聚合查询很考验索引能力这不是 REA 的问题而是所有流水型数据的共同规律。从代码角度看你可以把事件服务设计为一个工厂根据 event_type 创建对应的事件对象然后交给后续的处理器去更新资源和触发关联逻辑。这样新增加一种业务动作本质上就是新增一个事件类型和处理器比在订单状态机里到处加 if 分支要清爽得多。5.3 与订单中心、库存系统对照传统订单中心喜欢用订单头、订单行、状态字段来建模库存系统则常单独维护一张即时库存表。REA 模型在落地时可以这样做订单头订单行的信息仍然保留但把真正的“出库”“入库”“收款”这些业务动作独立成事件表。也就是说订单表退化为一个“业务契约”事件表才是“业务事实”。这个拆分乍看会增加表数量但从长期维护角度是值得的。契约本身会被修改、被变更、被作废而事件是不可变的。把这两种性质混在一张表里必然导致代码里到处处理“特殊状态”独立之后反而清晰。6. 这中间最常见的问题和排查口诀纸上建模很顺畅一到真实系统就会出现各种偏差。这一节把我的实际踩坑经验集中罗列出来多少能帮你省点试错时间。6.1 三类高频错误第一类是“把单当事件”。很多设计人员会觉得“新建订单”就是一个事件而且订单创建时确实会发生库存预占或状态变更。但严格说订单创建只是契约生成不是业务事件真正的事件是随后发生的支付、发货、收货。把订单推成事件的结果是所有统计都围绕订单状态展开一旦发生修改订单、取消订单逻辑就会纠缠不清。第二类是“把余额当资源”。资源表里放一个“当前库存量”字段每次事件来的时候执行 update 更新看起来天经地义其实副作用很大你失去了追溯历次变化的能力也失去了从任意时间点还原历史快照的可能。库存当前值应该是从事件明细聚合出来的而不是物理存储的既成事实。第三类是“事件表塞单据信息”。如果事件表里直接放订单编号、发货地址、物流公司这类附属信息短期用着方便长期会让系统难以扩展因为一旦单据类型增加事件表字段就会变成全字段的大杂烩。6.2 常见问题速查表问题表现可能原因排查思路某个商品的库存余额对不上某笔盘点调整事件没有记录检查资源变更事件是否都落event_line按时间统计销售数量时遗漏数据出库事件和销售事件各自独立记录没建立关联检查事件之间的引用关系某段时间报表数据出现负数余额信息被反推时用错了事件顺序检查事件的 occurred_at 和相关事件的先后依赖退货导致库存多出来但财务没收到退款退货事件和退款事件未关联检查退货事件是否指向退款事件或原销售事件新增一种业务类型要改很多表事件表里塞了太多业务专用字段把专用字段拆到扩展表保持 event 表通用6.3 两条有效排查思路排查 REA 模型是否健康我常用两个套路。第一个是“时间倒推法”。拿一个异常数据点比如某个商品的可售量往回查它的变化链路最后一次入库是什么时候最后一次出库是什么时候中间发生过多少次盘点调整每一笔是否都能指向一个事件。链路能完整走通说明事件装配基本没有问题。第二个是“角色填空法”。抽查一笔关键业务把事件列出来逐个填空参与者。如果发现“出库”事件只有内部仓管员而没有对应客户那就要赶紧补全因为一旦缺了外部参与者后续所有按客户维度的分析都会失真。这个方法在存量系统的数据修复中也非常好用。7. 我自己实践的收益和边界REA 并不要求你把所有系统都推倒重来它更像是一副思考框架。我现在的习惯是画任何数据表之前先用“资源、事件、参与者”在白板上把业务过一遍等三角色的关系清晰了再开始定表结构。这个步骤只用多花十几分钟却极大概率避免后续返工。实际收益最明显的一次是重做对账模块。原来每月对账要把订单表、支付表、退款表逐一关联光是异常分类的逻辑就写了几百行。改成 REA 思路后所有资金相关动作都被定义为事件对账就是按时间维度搜“收款事件”和“付款事件”再比对原始交易流水。整个模块的代码量少了一大截还顺便把“每一笔钱是谁付的、对应哪张单”这个问题给解清楚了。当然 REA 也不是万能药。它擅长处理“业务过程清晰、需要可追溯”的系统但如果你的业务本质就是一个内容平台核心是阅读、点赞、评论这类轻量行为上 REA 反而显得过重。另外REA 模型在企业系统里往往要和领域驱动设计、事件溯源这些方法论配合使用单独拿它解决所有架构问题并不现实。它更像是一个思考前置的透镜让你在设计之初就不容易丢掉“业务事实”。
阅读完成 · 觉得有帮助?
咨询建站