资讯动态

REA建模:让业务系统从“记结果”走向“记过程”

发布时间:2026/10/22 19:06:56 来源:尧图企业网站定制
前阵子接手了一个老旧的进销存系统重构业务方提了一个让我印象很深的要求不要只给我做一套记账工具我要能讲清楚每一笔业务是怎么发生的。当时我翻遍了企业内部所有资料最终在一个旧文档角落里看到项目代号rea。一开始我以为是某个人的名字简写后来才意识到这是 Resource-Event-Agent 的缩写一套比借贷记账更贴近业务本质的建模思路。如果你也正在做 ERP、财务中台、数据仓库相关的工作或者只是想让系统不再只记结果、不记过程那么这篇文章应该能给你一条新的思路。rea 不是一个具体的软件或框架而是一种业务建模思想。它把任何经济业务看成资源在事件中流转代理参与其中的结构化记录天然适合用来做进销存、订单、财务、审计等系统的领域建模。我花了大半个项目周期去理解和实践这套思路踩了不少坑也收获了很多下面就把这个过程完整拆开讲。1. 为什么我会盯上rea这个代号1.1 一次库存对账项目带来的线索事情是这样开始的某家商贸公司要统一订单、库存和财务三套系统的数据口径。过去三个系统各算各的订单系统管销售订单库存系统管出入库流水财务系统管应收应付和总账。老板问这个月的销售毛利到底是多少三个部门能给出三个数字。整个项目组都在找一种能同时覆盖这些业务场景的底层模型。翻资料时一份很老的《系统设计说明》里提到建议业务模块的基线模型参考 rea 思想。当时的系统负责人早已离职文档里也没有展开解释。我就顺着这个词去查发现 REA 的全称是 Resources-Events-Agents也就是资源、事件、代理三要素。这个概念最早出现在会计信息系统研究领域核心观点很直接与其把业务数据转换成借贷分录后存进账本不如直接保存谁在什么时间、用什么资源、做了什么事这样的原始记录。随着资料越查越深我发现很多后来的财务中台、业务中台设计本质上都在向 REA 回归把业务过程变成可追溯的事件流而不是只存储结果。这个方向跟我当时遇到的对账问题高度吻合于是决定在项目里小范围试点。1.2 REA 到底是谁提出的Resource-Event-Agent先把三个词说清楚Resource资源企业可以控制、有经济价值的东西比如商品、现金、服务、人员工时、知识产权。它不一定是实物也包括应收款这类权益。Event事件资源流入或流出企业的经济行为比如采购入库、销售出库、支付货款、收到退货。事件是 REA 模型里的最小记录单元必须记录发生的时间、关联资源和参与代理。Agent代理参与事件的人或组织可以是企业内部的员工、部门也可以是企业外的客户、供应商、物流商。代理是事件的执行者或受益者。这三者之间的关系就是一个代理发起或参与一个事件一个事件带动一个或多个资源的流转。销售出库这个事件由销售员和客户两个代理参与结果是把商品资源流出企业同时形成了应收账款资源。这样一条记录比借应收账款 贷库存商品要直观得多。1.3 这个模型解决的最原始问题REA 最早要解决的问题其实是会计上的一个老毛病复式记账记录的是一笔业务的会计解释而不是业务本身。采购一批货物传统分录是借存货 贷应付账款但存货和应付账款这两个账户状态是怎么产生的是从哪张采购单、哪个供应商、哪个验收人那里来的传统账本里没有完整的答案需要再依赖凭证附件去反查。REA 的思路是倒过来先把业务事件完整记录下来账务只是从这个事件记录里推导出来的一个视图。业务事件是源头借贷分录是下游产物。这种设计天然支持追溯每一笔财务数据都能顺着事件链找到最初的业务动作审计的时候不用再翻纸箱子了。这也是我认为 REA 值得花时间研究的原因它不是在教你多记一笔账而是在教你重新设计业务系统的信息架构。2. REA 模型的三个核心构件资源、事件、代理2.1 资源不是账户而是业务对象在 REA 模型里资源是一个很容易理解错的概念。它不是你财务科目表里的存货科目而是某一类可以被唯一标识的业务对象。同样叫库存商品财务科目是一个聚合概念把成千上万种商品汇总成一个数字但 REA 里的资源必须能定位到具体对象比如 SKU 编码为 XXX0001、批次号为 20250115 的那批 A 型号物料。如果资源没有唯一标识事件就无法精确陈述是什么资源流入了、什么资源流出了。因此落地时资源往往不是一张表而是一组表商品表、批次表、在途订单表、应收权益表等。资源需要有价值属性还要有状态属性可用、待检、已锁定、已转移。我见过很多项目失败的原因是把资源和账户混为一谈结果做出了一个既不是库存管理、也不是实时账本的四不像。2.2 事件业务过程的最小记录单元事件是整个 REA 模型的核心。在建模阶段你需要回答的问题是一个业务从开始到结束中间经历了哪些不可再拆的原子动作以采购为例可以拆成创建采购申请、下达采购订单、供应商发货、仓库收货、质检完成、财务付款、供应商开票。每一步都是一个事件每个事件都有明确的时间、参与者、资源数量和单价。事件之间还有先后关系比如收货必须发生在发货之后、付款通常发生在收货之后。这里的难点是原子的粒度怎么定。拆得太粗比如把收货混进订单完成里后续想统计到货及时率就要拆数据拆得太细比如把质检员拿起测试笔也算事件系统会变成噪音仓库。我的经验是以是否改变了资源的可用状态或所有权为准。只要资源状态变了或者权属变了就应该单独建一个事件。2.3 代理谁参与了这个过程代理就是业务里的人和组织但要比系统账号更语义化。一个员工账号可能对应不同的代理角色他可以是采购员、仓库管理员、销售负责人。REA 建议把代理独立建立让账号、角色、组织架构都作为代理的属性或关联关系而不是直接写死在事件表里。实际中这样做的好处很明显审计时可以回答这批退货是谁入库的、谁审批的而不是只能看到操作工号 00321。代理可以分层比如外部代理供应商和内部代理采购员都参与同一个采购事件。这种多代理的结构为设置权限和审批流提供了清晰的数据基础。2.4 三者之间的关联规则REA 模型并不是简单地把三类数据建三张表它还有几个关键的关联语义一个事件至少涉及一个资源且资源的变化方向要被标明增加还是减少。一个事件必须有至少两个代理参与通常是内部代理和外部代理或者两个内部代理。出库事件通常对应一个入库事件如销售出库对应客户收货形成交换闭环。事件可以形成链比如订单事件 - 出库事件 - 收款事件之间是因果关系不是并列关系。这四条规则在落库时体现为外键关系和流向字段。很多业务系统只做了资源变动表没有把代理和事件链建模出来所以只能回答库存少了多少不能回答为什么少了、是谁操作的。3. REA 和传统复式记账的底层差异3.1 复式记账本质是账户状态快照传统财务系统的中心是一张总账和多张明细账。每一笔分录都在更新账户余额借方加、贷方减。账户余额就像水缸里的水位分录只是不断往水缸注水或放水。你看到的水位只代表当前状态无法知道这缸水曾经被哪些管道流过、何时流过、为什么流进流出。业务系统如果完全按照复式记账的思想来设计往往会面临一个问题订单、库存、财务各自为政各有各的水位。对账的本质是比对两个水缸的水位或者把一批水位转换公式套到另一批水位上。一旦业务复杂转换公式里全是特例系统就变成了补丁叠补丁。3.2 REA 记录的是可验证的经济轨迹REA 不维护账户余额而是维护一条经济轨迹每一个事件是一个点每个点都记录了什么资源变动了、谁参与了、发生在什么时候。库存余额、应收账款余额、总账科目余额都可以通过把事件流按时间轴聚合后推算出来。这有好有坏。好的一面是任何一笔余额都可以下钻到业务事件不再是凭空出现的数字坏的一面是查询余额需要聚合计算不能直接读一张余额表。性能上必须用冗余、缓存、快照或物化视图来补但这也比数据口径不一致好解决得多。3.3 一张对比表借贷分录 vs REA 事件为了直观我列一下同一个业务动作在两种模型下的记录差异。对比维度传统借贷分录REA 事件核心记录借/贷科目金额资源增减、代理参与、事件时间记录目标账面余额业务事实可追溯性需凭证附件辅助事件链天然可追溯是否冗余存储过程过程隐藏过程即数据生成财务报表直接可用需聚合视图生成审计证据弱强这张表并不是说 REA 要取代复式记账而是说在业务系统阶段REA 比借贷分录更适合做底层语义借贷分录可以做成上层聚合结果而不是底层存储格式。3.4 从 REA 推导出复式记账佐证模型完备性我一开始也很担心REA 这么抽象最后能不能生成合规的会计凭证后来动手建模才发现REA 事件本身包含了足够的信息来推导借贷。比如销售事件产品资源库存商品减少对应贷方库存商品同时应收权益资源增加对应借方应收账款。REA 里每一次资源流出或流入都能对应到一组会计科目的增减。关键在于建模时就要把资源类型 - 默认会计科目的映射规则配置好。这个推导过程虽然要写额外代码但规则统一、便于解释。更重要的是当业务规则变化时只需要调整映射不需要返工底层几十亿条流水。这也是我最终敢在项目里试点 REA 的原因。4. 用一个贴近实际的案例从采购到付款的 REA 建模4.1 场景设定某公司采购办公耗材我以一个非常常见的场景来演示公司采购部向某供应商采购 100 箱 A4 纸每箱含税价 120 元合同约定货到验收后 30 天付款。货到后仓库验收入库财务在第 30 天付款。传统系统会记录采购入库单、应付凭证、付款凭证。REA 系统要记录的是这个业务发生了一串什么事件、资源如何流转、谁参与了。4.2 第一步识别资源、事件、代理先把业务对象归类资源100 箱 A4 纸商品资源、现金/银行存款货币资源、应付权益负债资源。事件采购订单确认、供应商发货、仓库收货、财务付款。代理采购员内部代理、供应商外部代理、仓库管理员内部代理、出纳内部代理。这里需要注意的是应付权益其实是一个抽象的权益资源它在收货事件后从零增加为 12000 元在付款事件后归零。把权益也建模成资源是 REA 走向财务系统的关键一步。4.3 第二步画出事件流文字描述不用图事件流可以用列表描述下达采购订单采购员创建订单确认资源需求和供应商。供应商发货外部代理发起发货事件指向商品资源。仓库收货仓库管理员确认入库商品资源流入企业。财务付款出纳支付 12000 元货币资源流出同时应付权益资源减少。每一步都要记录事件编号、事件类型、发生时间、参与代理、关联资源编号、资源数量、资源方向。这样即使将来有争议也能沿着付款 - 收货 - 订单这条链把所有凭证重演。4.4 第三步落地为表结构实践中我不建议照抄任何一张万能 REA 表而是根据业务场景设计分表。核心可以有以下几张-- 事件表 CREATE TABLE rea_event ( event_id UUID PRIMARY KEY, event_type VARCHAR(64) NOT NULL, -- 订单确认/发货/收货/付款 occurred_at TIMESTAMP NOT NULL, location_code VARCHAR(32), UNIQUE (event_type, occurred_at, related_ref) ); -- 资源变动表每事件可能有多条资源变动 CREATE TABLE rea_resource_flow ( flow_id UUID PRIMARY KEY, event_id UUID REFERENCES rea_event(event_id), resource_type VARCHAR(64) NOT NULL, resource_id VARCHAR(64) NOT NULL, direction VARCHAR(8) NOT NULL, -- IN/OUT quantity DECIMAL(18,4) NOT NULL, unit_price DECIMAL(18,4) NOT NULL, currency_code VARCHAR(16) NOT NULL ); -- 代理参与表 CREATE TABLE rea_agent_partipation ( event_id UUID REFERENCES rea_event(event_id), agent_id VARCHAR(64) NOT NULL, agent_role VARCHAR(64) NOT NULL, PRIMARY KEY (event_id, agent_id, agent_role) );这只是一个最简单的落地形态。真实项目里还要为每个事件类型建业务扩展字段比如订单确认事件要关联合同编号收货事件要关联质检单号。一张通用表确实灵活但查询和约束都会变得很难维护。4.5 第四步如何从事件数据生成会计凭证有了上面的表生成凭证就不难了。写一个聚合服务按资源类型的映射规则推导借贷def derive_accounting_entries(event_id): flows get_resource_flows(event_id) entries [] for flow in flows: if flow.direction IN: # 商品入库 - 借库存商品贷应付权益 entries.append((DEBIT, map_asset_account(flow.resource_type), flow.quantity * flow.unit_price)) entries.append((CREDIT, map_liability_account(flow.resource_type), flow.quantity * flow.unit_price)) elif flow.direction OUT: # 付款 - 借应付权益贷银行存款 entries.append((DEBIT, map_liability_account(flow.resource_type), flow.quantity * flow.unit_price)) entries.append((CREDIT, map_bank_account(flow.currency_code), flow.quantity * flow.unit_price)) return entries当然这只是示意实际还要考虑税金、折扣、运费分摊。但重点在于业务数据在先会计数据在后且会计数据可以随时根据规则重新生成这让历史口径调整变得异常轻松。5. 在业务系统里落地 REA 时容易踩的坑5.1 不要一开始就设计一张超级大表我刚试点时曾想设计一张通吃所有业务的宇宙事件表把订单、库存、财务全塞进去。结果连约束都写不完整外键关系越来越乱最后还是拆成了领域事件表再通过统一事件编号关联。一个更稳妥的做法是按业务域拆事件表同时保证每个事件都有一个全局唯一 ID 和统一的事件头表。事件头只存通用字段发生时间、类型、代理详情放到领域子表。这样既能保持 REA 的语义统一又不会让某个子域的变化影响全局。5.2 资源标识符和物料编码的关系资源标识符不是简单放一个物料编码就完了。同一 SKU 可能有不同批次、不同仓位、不同价格。如果资源标识符只到 SKU 级别库存流水会算不清成本。建议资源标识符至少包含 SKU 批次 仓库/仓位如果业务需要。在事件流里这些字段必须写死不能等后续报表再关联否则事件链会断。5.3 事件的时序与状态机的处理事件除了有发生时间还有状态草稿、已确认、已取消、已冲销。REA 强调的是记录实际发生了什么但系统推进过程中会有人为改单、返工、作废。这时候不要直接改事件而是新增一个冲销事件把原事件标记为失效同时记录一个更正事件。这个模式很像会计上的红冲保留涂改痕迹用新事件纠正旧事件。一开始这样做很别扭总觉得多存了一条无用数据但后来做对账和审计时发现正是这些冗余帮我们还原了真实业务动作。5.4 权限模型与代理之间的关系业务系统里的用户账号不等于代理。一个用户可能既做采购申请又做收货但在 REA 里这是两个代理角色。如果权限直接绑在账号上后续系统要支持一个人分管多个公司主体时就会乱套。建议把代理单独建表账号通过人员与代理关联代理再绑定资源和事件的操作权限。这样任何一笔事件都能回答是哪个主体下的哪个代理做的权限审计也会简单很多。这个设计在项目上线后帮我们省掉了很多合规问题。5.5 如何处理冲销、退回、折扣等异常事件异常事件是落地 REA 时最大的难点。销售退货、采购折让、价保补差这些并不是资源的简单反向流动它们往往还涉及费用分摊、税额调整、应收/应付重估。我总结的经验是不要试图让一个事件承载所有业务语义而是把它拆成多个动作。比如退货拆成退回商品入库和退款确认两个事件再把这两个事件关联到原始销售事件上。这样余额能对上事件链也不会断裂。虽然事件数量变多了但每个事件保持原子性后续所有统计口径都会清晰。6. 扩展方向超越财务会计走向业务本体6.1 REA 是事件溯源的一种语义层如果你了解过事件溯源Event Sourcing你会发现 REA 和它有不少共鸣都是把业务变化记录成不可变事件而不是直接保存状态。区别在于事件溯源通常以系统操作为记录主体而 REA 以经济语义为记录主体。例如同样是订单状态变更事件溯源记录订单状态从待付款变为已付款REA 会记录客户支付了 12000 元资金资源流入企业同时获得订单商品的权益。后者更贴近业务本质对中台和数据分析更友好。所以如果要做财务中台REA 相当于给事件溯源加了一层会计级的语义规范。6.2 在微服务架构里的应用微服务拆分最容易出问题的就是跨服务的数据一致性。订单服务、库存服务、财务服务各自维护数据库靠接口同步数据经常出现哪条消息丢了、哪张表数据不匹配。REA 可以作为一种服务间事件的公共协议。每个服务不直接更新对方的表而是发布符合 REA 语义的事件比如商品出库应收权益增加其他服务订阅事件后在自己域内聚合成所需视图。这样一来服务间通过事件语义协作而不是通过私有的 RPC 暴露字段。我实际落地后的感受是服务之间确实多了一层翻译但换来的是业务语义的统一后续新增对接方的成本大幅下降。不过要注意REA 事件的消息体需要做版本管理否则前后端语义一变消费方全得跟着改。6.3 与数据中台、行为日志的配合很多数据中台会把业务日志和埋点日志混在一起结果分析时不知道该信哪个数。如果用 REA 事件作为中期标准数据埋点日志和外部行为数据可以作为补充维度关联。比如 APP 上用户点击了某个商品页行为日志随后下了一个订单REA 事件点击轨迹提供了为什么REA 事件提供了发生了什么。这样的分层能让分析与核算既有因果解释又有事实依据。6.4 未来自动生成财务报表、智能审计REA 最终让我看到最有想象力的一点是如果底层全是一串完整、不可变、按时间轴展开的业务事件那么财务报表只是这些事件的一个聚合视图。月初结账时不需要再人为调账只需要重新执行一套映射规则就能生成利润表、资产负债表和现金流表。更进一步的智能审计也是同理审计规则变成针对事件链的查询脚本异常检测可以直接从源头发现哪个代理在非工作时间转移了高价值资源而不是在财务凭证里大海捞针。虽然这套体系在传统企业里推进起来阻力不小但一旦推行成功财务团队的月末加班文化会明显改变。我自己的体会是rea 这三个字母表面上像一个缩写实际上更像一种提醒——业务建模的初心是先搞清楚发生了什么而不是先去算账户余额。如果你也在做系统重构可以从一个最小场景开始把订单、库存、财务这三条线都还原成事件流。试过以后你会发现很多过去解释不通的对账差异在事件链上一下子就能定位到源头。这个方向值得多投入一点时间。

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

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

免费获取报价 →
↑