资讯动态

REA模型落地指南:用资源-事件-代理统一业务建模与对账

发布时间:2026/10/14 8:46:01 来源:尧图企业网站定制
1. 项目定位一个模糊代号怎么锁定业务对象模型如果只扔给我“rea”三个字母平时打交道的读者很容易把它当成某个目录名、“React 应用”的随手缩写甚至是一个没写完的单词。但我自己看到之后优先想到的是“Resource-Events-Agents”也就是“资源-事件-代理”业务建模模型。为什么会往这个方向收因为这三个词正好戳中了我这些年做业务系统时最常遇到的那一类问题账目和业务动作长期分离数据表越建越多却越来越说不清一笔钱、一批货、一个客户之间到底是什么关系。REA 模型最早是从企业会计语义研究里长出来的后来被广泛用在领域建模、事件溯源、系统对账等领域。它的核心思路非常朴素把业务世界看成三类对象的组合。资源是会被交换、消耗、生产出来的东西比如现金、库存商品、原材料事件是让资源发生转移或被改变的实际业务动作比如采购入库、销售出库、收款代理是参与事件的组织和个人比如客户、供应商、仓库操作员、系统机器人。如果项目代号叫“rea”我基本会假定作者想表达的就是这个三角结构希望用一套统一语言把订单、库存、财务全面串起来。它能解决什么问题用一句话概括把“记不记得住账”从一堆互相耦合的表结构里抽出来改成先记录现实事件再按需生成各种业务视图。它适合谁来参考一句话描述适合那些每天被业务部门追着改报表、被财务要求补凭证、被老板问库存到底准不准的开发团队和独立开发者。这篇文章里的所有内容包括类型定义、事件表结构、视图投影、对账思路都是围绕这个目标展开的可以直接拿去套自己项目的核心域。1.1 我判断“REA”是在说 Resource-Events-Agents 的三个依据第一个依据来自软件领域里对这个缩写的约定俗成。在会计学术圈、语义网和领域驱动设计相关的社区里REA 经常被作为“资源-事件-代理”本体的正式缩写使用相关论文和企业建模案例已经积累了很多年不是生造的术语。第二个依据是业务诉求的普遍性。只要做过进销存、订单、支付、生产制造一定会在某个版本里被“对账”折磨得够呛而 REA 恰好是能把业务行为和资金变动建模到同一个骨架上的一条成熟路径。第三个依据更实际在事件溯源、CDC变更数据捕获、物化视图这些技术普及之后“先落事件后投影”已经成为主流设计REA 刚好给这些事件定义了稳定的骨架资源是谁、代理是谁、流入流出方向、时间戳、幂等约束。我当然不是说“rea”这三个字母只能解释成这个模型。如果你的项目确实另有含义这篇文章里关于落地方式的思路依然通用因为核心方法是解耦的事件先行的取舍、资源粒度的判断、财务口径的处理这些内容不依赖特定业务方向。从单个含糊标题解构出一套可执行建模方案本来就是我们这种老从业者平时做的事。1.2 它解决的问题一句话和一张表说清楚传统业务系统最常见的毛病就是同一个事实被复制到多张表里维护。订单有一张订单表库存有一张流水表财务有一张凭证表表面上大家都能用实际上口径经常对不上月底库存对不上应收对不上甚至一笔销售被不同部门算成了两种金额。REA 的思路是反过来的所有业务只记录“发生了什么”也就是事件事件两侧挂上资源和代理谁要什么数据就从这个事件流里投影出什么数据。对比项传统“增删改查计算表”REA“事件投影”核心数据订单、单据、科目余额业务事件、资源流、代理关系修改方式直接更新字段、补计数字新增事件历史不可篡改对账方式多表 join 后人工核对同一事件流生成多个视图口径统一审计能力依赖操作日志往往不完整事件天然就是审计日志学习成本低初期偏高中期明显下降这张表里最值得注意的不是技术选型而是思维转变。REA 本质上不关心你用什么数据库、什么框架它首先是一种分析业务的语言。语言统一了后面所有系统和代码层面的统一才有基础。2. 核心原理为什么资源、事件、代理这套三角能覆盖真实业务2.1 传统“科目表借贷分录”的别扭之处做传统财务模块第一件事是定科目表应收账款、库存商品、主营业务收入、应付账款……每一笔业务先翻译成借贷分录再写进总账。科目表的好处是规则统一坏处是它把丰富多彩的业务世界拍平了。客户余额、销售成本、库存变化这些在业务人员口中很自然的问题被强行拆成若干个科目上的数字想看全貌必须翻凭证、查明细、再加工不少口径。更麻烦的是多业态场景。卖软件的、卖硬件的、做服务的都要并表科目表要么越补越稀疏要么一套科目下塞了无数自定义项。不同部门用同一个科目时理解还不一样月底对不上账成了常态。我在一些项目里见过最极端的情况光“其他应付款”下面就有几十个明细科目业务含义早就丢了只剩下数字在流动。REA 把视角倒过来了。它不先问“借什么、贷什么”而是先问“发生了什么”。发生了什么事意味着有哪些资源进来、哪些资源出去、谁参与。借贷分录、科目余额、利润表这些都只是从事件流里推导出来的观点而不是原始事实。观点可以变事实不能变。这个差别是很多对账问题的根源也是 REA 值得引入的根本理由。2.2 把“账上有什么”和“实际发生了什么”拆开做建模时我一般会画一个很简单的三角关系代理参与事件事件影响资源。比如一个新客户“下单”这个事件参与的代理分别是客户和本企业订单会创建一项“应收账款”资源同时让“库存商品”资源流出。这里最关键的点不是用什么符号而是要理解“流出”和“流入”都必须落到具体资源上。没有资源变化的动作在 REA 里不叫业务事件。用生活例子解释更清楚。买一杯咖啡咖啡是我得到的资源钱是换出去的资源店员和我是代理点单和付款是两个先后事件。一次买卖最终改变的是双方各自资源池里的数量。把场景放大到采购、生产、物流、支付逻辑完全一致业务链路再长本质也是资源和代理之间的互动序列。术语上“存量”是指某类资源在某个时点剩下的数量“流量”是指某个事件让资源发生的增减。库存、应收、现金余额都是存量而采购、销售、收款这些动作产生的都是流量。REA 的核心机制就是让每笔流量有据可查进而让每个时点的存量都能精确推导出来。相比传统系统“改了字段就算完”的处理方式这个机制给了业务和审计极大的安全感。2.3 和事件溯源、领域驱动设计的关系这里要说清楚一点REA 不是框架也不是现成组件它是一个分析模型。正因为它不是框架所以它可以和事件溯源、CQRS、领域驱动设计这些方法论自由组合。组合之后的威力相当大。在 DDD 的事件风暴里团队经常遇到一个尴尬场景大家写了一堆领域事件但每个事件该带哪些字段、以什么粒度出现讨论半天没有结论。REA 提供了一个稳定模板每个业务事件至少要有涉及到的资源、流入流出方向、涉及的代理、发生时间。有了这个模板事件设计就不再是拍脑袋。事件溯源落库时事件表只需保证追加、不可变、有序REA 又恰好定义了资源池和代理角色查询端投影出来的视图自然就对应库存、应收、收入这些业务概念。我自己做系统时也发现很多团队不是不想用事件溯源而是害怕事件结构定义得太随意以后没法演进。用 REA 定边界事件结构会稳定很多。这也是为什么我常跟同行说不要在项目一开始就引入一整套事件溯源框架而是先用 REA 把业务事件理清楚框架只是工具模型才是根。3. 概念落地把 REA 翻译成数据结构3.1 最小类型定义与字段解释很多人看 REA 觉得理论味太重不好落地。实际上翻译成代码并不复杂我平时用 TypeScript 写微服务落地一套 REA 的最小类型大概下面这个样子// 资源会被交换或改变状态的对象 type Resource { id: string; kind: inventory | money | receivable | service_hours; name: string; }; // 代理参与事件并能承担责任的主体 type Agent { id: string; kind: customer | supplier | internal; name: string; }; // 资源流一个事件给某个资源带来的变化 type Stockflow { resourceId: string; delta: number; // 正数为流入负数为流出 unit: string; }; // 业务事件一段可审计的业务事实 type BusinessEvent { eventId: string; eventType: string; happenedAt: string; inflows: Stockflow[]; outflows: Stockflow[]; participants: Agent[]; refId?: string; // 外部业务单号例如订单号或发票号 };有人会问为什么要把流入和流出分开而不是只给一个正负号实际场景里一个事件经常同时影响多条资源流。比如“采购收货”会让“存货”增加同时让“应付账款”增加两条流的业务含义完全不同分开表达更自然。底层存储时你可以直接把 Stockflow 平铺成一张流水表用一张表存事件一张表存资源流再加一张表存代理参与关系。代码写法可以灵活变化但语义要保持跟 REA 对齐。3.2 流量变存量增量减量的计算口径REA 最核心的计算是“流量累积为存量”。拿库存举例每一次采购入库是“存货资源池”的流入每一次销售出库是流出某个时刻的存量就是所有已发生事件流量的加权汇总。做核对系统时这个口径特别舒服因为它天然具备准确性只要事件记录不丢、不重复存量自然就对。传统系统里每次手工更新“库存数量”字段一旦某个更新漏掉或重复执行数据就歪了。更关键的是流量累积出来的存量是实时可算的。订单、支付、物流这三个系统如果各自维护一份数量月底极易对不上但如果它们共享同一份事件流就根本不存在“哪张表里数量多一个”的问题。应收、库存、销售成本、现金余额全部可以用同一套机制推导对账从“事后排查差异”变成了“事件流本身一致”。3.3 从事件流推导会计分录严格说 REA 不是复式记账但它能生成复式记账。落地的做法是写一个投影函数把每个销售事件映射成“借应收账款、贷主营业务收入”把出库事件映射成“借主营业务成本、贷库存商品”。只要事件类型定义得清晰会计凭证就是事件流上的纯函数计算业务部门再也不用手工补录凭证。这里有个重要边界财务上很多事项跟具体业务事件不是一回事比如折旧、坏账计提、跨期分摊。处理这类事项不能去改历史事件而是新增一个“会计调整事件”让事件流本身保持业务事实的原貌。我在做财务对接时一般会把事件流分成两层业务事件层和会计调整层。投影报表时先按业务事件生成基础分录再叠加调整事件。这样既保证原始业务可审计也满足财务口径的灵活性。4. 实操过程一个制造业场景的完整建模步骤4.1 第一步用动作清单摸清业务不要一上来就画 ER 图也不要先设计表先把业务动作全部列出来。我习惯用一个虚构的零件加工场景来做示例某家小型制造企业从供应商采购铝块加工成配件再销售给客户。把这个场景拆开看核心业务动作大概是六类采购收货、采购付款、领料出库、完工入库、销售发货、客户回款。列完动作之后给每个动作标上三个阶段的问题谁参与、动了哪种资源、资源的流入流出方向是什么。比如“销售发货”这个动作参与的代理是客户和本企业行为是让“库存商品”流出同时形成一项“应收账款”资源。整个过程不涉及任何科目不受科目表限制业务人员一听就懂。这一步做完模型基本上已经出来了。整套模型会稳定在几十个事件类型以内不会因为新加一个销售渠道就崩掉。新渠道只是事件数据增加事件结构不用大改。这也是 REA 面对多业态时比科目表灵活的原因。4.2 第二步定义事件表、流水表和幂等约束落库时我没有把事件以 JSON blob 一把梭存起来而是拆成两张核心表一张业务事件表一张资源流水表。再加一张参与表记录代理。下面这套建表 SQL 已经在我本地多个项目里跑过可以直接抄走create table business_event ( event_id bigserial primary key, event_type varchar(64) not null, occurred_at timestamptz not null, ref_no varchar(64) not null, -- 业务单号例如 SO-2025-001 created_at timestamptz not null default now() ); create table stock_flow ( flow_id bigserial primary key, event_id bigint not null references business_event(event_id), resource_id varchar(64) not null, delta numeric(18,6) not null, -- 正数流入负数流出 unit varchar(16) not null ); create table agent_participation ( event_id bigint not null references business_event(event_id), agent_id varchar(64) not null, role varchar(32) not null -- buyer, seller, warehouse... ); alter table business_event add constraint uq_event_ref unique(event_type, ref_no); -- 幂等约束这个唯一约束是踩过不少坑之后加上的。消息队列在分布式环境下重试很常见如果不做幂等同一个销售发货事件被消费两次库存就会少算两笔财务也会多记一笔收入。以“事件类型业务单号”做唯一键重复投递时直接忽略或覆盖为同一事件ID问题就解决了。流水表里没有单独的“方向”字段而是让 delta 的正负表达方向。负数为流出正数为流入。这个设计有个好处查询存量时直接按资源分组求和即可不需要再判断方向。业务事件本身只记录事实哪个资源增加、哪个资源减少都清清楚楚。4.3 第三步用视图直接还原库存、收入与应收建好表之后业务查询就变成了视图层面的投影。比如要算某个商品此刻的库存写一条 group by 的汇总create view inventory_snapshot as select resource_id, unit, sum(delta) as available from stock_flow group by resource_id, unit;要算一段时间的销售额也简单按事件类型过滤后做流入流出统计。更复杂的应收余额计算本质也是把“销售发货”事件中的应收账款流入和“客户回款”事件中的流出做汇总。所有口径都来自同一套事件流各部门看到数字不会再打架。有人担心这种实时聚合在数据量大时扛不住。实际上可以分两步解决日常查询走物化视图底层用一个定时任务把新旧事件合并到快照里只有需要审计追溯时才去重新拉全量事件流回放。这样既保证实时性也给财务留下了完整流水档案。4.4 第四步留一条回放与审计通道REA 模型落地到生产环境之后最值得花时间做的功能是“事件回放”。所谓回放就是把某段时间内的业务事件从库里读出来重新应用投影规则生成那一时刻的库存、应收、利润快照。它的价值在于任何一笔对不上的历史账都能沿着事件流找到根因而不是像传统系统那样只能对着最终数字猜过程。实现方式并不复杂核心是两套机制。一是“事件按全局序号递增”保证回放顺序是确定的。二是“定期打快照”比如每天零点落一份各资源存量的快照回放时从最近一份快照开始而不是每次都从头重算。我在项目里通常用一张快照表记录 snapshot_id、resource_id、total、as_of_date回放接口拿到起止时间后先找最近的快照再叠加增量事件速度和准确度都很可观。5. 常见问题与排查技巧实录5.1 资源粒度到底切多细最常被问的问题是库存资源到底按 SKU 建还是按批次、序列号建我的经验是看业务分析的最小需要而不是数据库建表最方便。如果只是采购、销售这种正常流转按 SKU 建模就够了如果有批次、保质期、追溯要求就把“批次”这个维度放进资源池如果每台设备都要单独跟踪维护记录那序列号才是资源粒度。这里有一个比较明确的避坑点不要一开始就把资源切到“不可再拆”的粒度。粒度越细事件数量越多查询投影和事件回放的压力都成倍上涨。可以先按主流业务口径建模后续真出现批次追踪需求再通过设计“资源拆分事件”把粗粒度资源细化为细粒度资源这种演进在 REA 模型里是非常自然的不会破坏历史事件。5.2 别把“改数”当“事件”很多团队第一次接触 REA 时最容易犯的错是把“修改订单金额到 100 元”这类更新操作当成一个业务事件去记录。区分标准很简单业务事件必须源于现实世界的变化比如客户支付、货物发出而“改单”是修正之前记录的错误属于系统整理动作。如果你把改单当成普通事件再和业务事件混在一起做投影很快数据就会出现双算或漏算。正确做法是对“修正”单独建模。一个新的事件类型叫“订单金额调整”它只影响“应收账款”资源的流动不影响时间轴上其他事件。历史事件不允许 UPDATE新调整只能追加。实践久了你会发现这个原则带来的可审计性是其他设计很难替代的。5.3 性能实时累计不是问题别慌有些人看到“所有存量都从事件流推导”第一反应是性能会崩。实际上没这么严重。事件表可以像普通流水表一样按月分区查询走索引日常库存、应收查询走物化视图不用实时全算。只有月初对账、异常追查这种低频场景才需要回放部分事件流逻辑也简单。我现在用的标准做法是三层事件表保存全量明细快照表保存每日汇总物化视图保存当前实时状态。写入事件时同步维护快照和物化视图用事务保证一致。生产环境里几千 TPS 的事件量这套结构完全够用。如果事件量再大几个量级把事件写入和投影刷新拆分到不同服务用异步订阅去刷新物化视图即可。5.4 财务口径和业务事件有冲突时财务上有很多口径是业务事件里没有直接体现的比如固定资产折旧、坏账计提、增值税申报差异、跨期收入分摊。最常见的问题就是财务人员觉得 REA 少了东西于是要求业务加事件结果把业务表搞得很复杂。正确做法是在事件流之上增加“会计调整层”用专门的调整事件表存储这些会计加工信息。我在项目里的调整事件结构基本一样event_type 改成“accounting.adjustment”ref_no 指向被调整的原始业务单号delta 表示金额再挂一个 adjustment_reason。业务报表和财务报表面向的投影函数不同但底层事件源完全一致。这样既能保住业务事实的纯净又能让财务口径独立演化。6. 哪些场景适合引入 REA哪些场景要慎重6.1 值得用的系统有几个共同点复盘这个 REA 项目后我总结出适合引入 REA 的系统特征跨模块对账要求高、业务变化快、需要审计、需要统一财务和业务口径。具体来说只要你的系统同时涉及订单、库存、资金中任何两项并且这些数据经常要对不上REA 就值得试。反之如果系统只是一套内部小工具数据量小、变化少、没有审计诉求那我建议保持传统 CRUD 设计不要为了“先进”给自己增加无谓的复杂度。拿之前一个进销存项目来说真正值得用 REA 的只有库存、采购、销售、资金这四个核心域而那些管理后台的字典表、用户配置、日志功能完全不需要。核心域用事件建模外围域保持普通表这也是我认为最优的折中方式。6.2 如果不做全量事件溯源怎么折中有些团队一听事件溯源就怕因为历史系统已经在跑不可能推倒重来。我的建议是可以做“半 REA 落地方案”传统表保留给非核心域但核心链路改成事件表。比如订单和发货可以保留原表但增加一张 slim 版的 stock_flow 表把库存和应收的变化全部实时同步进去。这样对账时只需要查这张流水表而不是把订单、出库单、发票多张表 join 起来核对。这种折中方案的好处是改动小、见效快。毕竟对线账而言最重要的不是立刻抛弃所有旧表而是先用相同口径把变化记录下来让所有人看到同一个数字。等到团队和业务都适应了这种口径再逐步把旧表废除平滑切换到完整 REA。6.3 给团队的一条实操落地节奏基于这些经验我更倾向给团队定这样一条节奏第一周只做业务事件清单不动代码第二周只落地事件表和一张资源流水表第三周接上一个物化视图让财务和库存看板开始读新口径最后再逐步替换原有报表。每一步都小到可控又都能立刻看到价值。踩过不少次坑之后我越来越觉得 REA 最有价值的地方不在于“先记事实后投影”这个技术决策而在于它倒逼团队把业务语言统一了。开会时大家在白板上画出来的图不管技术是否懂会计都能指着“这个事件动了哪份资源”来讨论问题这才是真正少走弯路的开始。后续如果继续扩展可以加上更多的资源类型和代理角色往业务流程引擎、AR/AP 自动核对、甚至跨公司合并报表方向延伸底层这套事件模型都能继续支撑。

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

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

免费获取报价 →
↑