第一次听到 REA 这个缩写是在一次进销存系统重构的评审会上。业务方坚持要把客户订单和发货单合并成一张业务单据表技术方则认为订单是承诺、发货是执行必须分开。两边吵了两轮谁都没法说服对方最后有人翻出一篇二十多年前关于企业建模的旧文献里面的 REAResource-Event-Agent资源-事件-参与者模型把这个问题回答得干干净净。作为一个常年和数据模型较劲的人我后来把 REA 这套思路又系统地研究了一遍发现它对做 ERP、进销存、业财一体化和数据中台的团队来说价值被严重低估了。这篇文章不打算讲纯学术理论而是从实际建模的角度把 REA 拆开揉碎讲清楚它解决什么问题、怎么落地、有什么坑以及什么时候不该用它。适合正在设计业务系统的开发、产品经理和技术架构师参考。REA 模型最早来自会计信息系统领域。上世纪八十年代初期一位研究会计系统设计的学者发现传统的借贷记账法虽然在财务核算上行之有效但它把业务过程压扁成了科目金额的抽象记录导致信息系统很难回答这笔销售是谁经手的、对应的发货实际发生了什么、为什么库存和财务对不上这类过程性问题。他提出的 REA 模型直接用资源、事件、参与者三类概念描述经营活动的全貌后来被大量研究者和架构师当作业务建模的基础框架。到今天很多讲数据中台、业务事件台账的资料背后的思想源头都能追溯到 REA。1. 那场把订单当账本的争论REA 要解决的问题1.1 订单到底是不是一张凭证先把这个最常见的争论场景讲透。做进销存或者订单系统的团队几乎都遇到过这样一个问题客户下单以后订单数据到底算业务单据还是算记账凭证如果按传统复式记账的思路来设计订单往往会被处理成一张待结算的凭证草稿等到发货、开票、收款了再往凭证里补充分录。这么做在财务视角下没问题但对业务系统来说隐患很大。因为订单在未发货之前本质上是卖方对客户的一个承诺它代表我们答应在未来某个时间交付这些商品。承诺和交付是两件事把它们当成同一个对象来管理就会出现一种尴尬业务员想看这个月接了多少订单翻出来的却是已经发货甚至已经回款的记录库房想看还有多少货要发又得在凭证中间做各种不算自然的过滤。REA 在这一点上非常清晰订单属于承诺事件发货属于执行事件两者在语义上必须分开建模。用生活化的比喻来说承诺是一份借条执行是还钱借条和还钱是两回事不能合并成同一条记录。这个区分是 REA 给我的第一个启发也是我认为它比大多数自定义业务单据模型高明的地方。1.2 传统建模为什么把过程搞丢了再往深一层看传统凭证-分录-科目这套叙事的核心目标是保证财务结果借贷平衡而不是还原业务过程。它天生倾向于把事件拍扁一笔销售在凭证里体现为借应收账款贷主营业务收入至于货物是谁发的、客户是哪天签收的、这单的毛利贡献是多少凭证系统一概不关心。这种只记录结果、不记录过程的设计在信息化早期没有太大问题因为那时候系统的边界就是财务部。但今天大多数业务系统要支持的是从前端获客、订单履约、库存调度到财会对账的全链路协同。如果底层的核心数据模型仍然是凭证化的那所有过程性的问题——比如这批货是哪位销售跟进签下来的客户签收和财务入账之间差了多少天库存账和实物账为什么对不上——都要靠额外加字段、加中间表来补救补来补去业务人员和技术人员的理解就分裂了这就是很多系统里字段含义全靠猜局面的根源。1.3 换一个视角从记结果到记过程REA 提供的是另一种叙事不先问这笔业务该怎么记分录而是先问什么资源、通过什么事件、在什么参与者之间发生了流动。资源是需要管理的有价值对象事件是经营中真实发生的活动参与者是活动的主体。三者加上关系就构成了业务过程的事实底稿。我整理了一个简短的对照表每次团队里讨论建模口径时我都会拿出来做参照对比维度传统凭证-科目视角REA 视角核心记录对象凭证、分录、科目余额资源、事件、参与者对订单的看法待核销的凭证草稿一个承诺事件对发货的看法影响库存与成本的分录来源一个执行事件资源流出对客户回款冲销应收的分录一个执行事件资源流入核心问题每一笔凭证算得平吗这一串事件完整还原了业务吗从这个对照可以看到REA 没有否定财务核算的必要性它只是把过程语义放回了核心位置。财务凭证可以当作 REA 过程之上的一个结果视图来生成而不是唯一的事实来源。这是我后面落地表结构时的基本思路。2. 资源、事件、参与者三要素的语义边界2.1 资源不是主数据表里那一行很多朋友听完资源、事件、参与者之后第一反应是资源不就是物料表嘛事件就是流水表嘛参与者就是客户和员工表嘛。这个理解大方向对但少了一层关键含义。在 REA 里资源必须是能够在交换或转换中被估值、被让渡、被消耗的对象。商品库存是资源货币资金是资源仓库里的一次维修服务能力也可以建模为资源某位资深顾问的一小时顾问时间同样可以建模为资源。判断一个对象是不是资源要看它是否参与了一方给出、另一方接收的完整业务动作。员工的个人信息表虽然也是主数据但它在 REA 模型里通常是参与者的属性而不是资源因为员工本人不会作为商品被让渡。物料主数据之所以是资源是因为它参与了一个完整的买卖交换。这个界限很多人容易画错。我在实际建模时还有一个体会资源的定义要和业务片段的边界对齐。比如在做供应链协同系统时产能可以被定义为可排产的工时资源但在做销售结算系统时工时资源又可能退回到参与者的能力属性。资源不是客观存在的某个表而是建模者根据业务问题划出的一种视角。2.2 事件业务真正发生的那一下三要素里事件是最容易理解、也最容易被设计烂的部分。在 REA 框架里事件描述的是经营过程中真实发生的活动必须具备时间、涉及资源、涉及参与者等事实属性。它是一段业务过程的动词而不是名词。关键要区分两类事件。一类是承诺事件比如销售接单、采购下单它表示未来会发生什么另一类是执行事件比如发货、收货、收款、付款它表示现在实际发生了什么。把两者混在一张表里是建模阶段最常见的错误。我的习惯是在设计阶段就给每个事件表打上一个标签是承诺还是执行承诺事件再发展成执行事件时要用外键引用对方的编号而不是直接覆盖原记录。用大白话说承诺事件是计划执行事件是事实。计划可以变更事实不能。2.3 参与者谁能做业务动作参与者指拥有决策权、能够发起或接受业务活动的单位和个人。它可以是一个人、一个部门、一个组织也可以是系统里的一个自动化角色比如自动派单服务。REA 特别强调参与者在交换关系里通常是两两成对的有卖方就有买方有授予方就有接收方。这个成对出现的特性对权限设计和责任追踪非常实用。订单表里记录销售员是谁发货表里记录仓管员是谁收款表里记录谁确认了这笔款项——这些参与者字段不是冗余而是还原业务事实的必备上下文。很多管理核查类查询能够通过参与者连线直接回答某个人主导了哪些业务事件在设计上比散落各处的operate_id字段要系统得多。2.4 三要素串起来的完整画面用一个最小例子演示。一家公司把一批现货卖给客户资源商品卖方库存里的存量货款买方账上的资金事件销售订单承诺未来交付发货执行商品从卖方流向买方收款执行资金从买方流向卖方参与者销售员卖方内部发起人、客户买方、仓管员卖方执行人这个画面里任何查询都可以落回三要素的组合某位客户在什么时候收到了我们发出的什么商品我们为此收到了多少钱经手人是谁。这种表达能力就是 REA 的核心价值。下面我会演示这样一张画面如何一步步落成真实的数据表结构。3. 复式记账够用为什么还要 REA从照片到录像3.1 复式记账的底层逻辑先承认一件事复式记账在商业史上足够伟大。它用有借必有贷的平衡机制保证每一笔业务都能从两个侧面被捕捉从而完成完整的价值核算。今天全球绝大多数企业账目底层都是这套机制。但复式记账有个本质特征它是一张快照。一笔交易只要被记成借贷分录业务的时序、参与方、资源流动路径就都被压缩成了两个数字。系统能回答这笔业务金额多少却很难回答这笔业务是怎么一步步发生的。就像一个摄像机只保留最后一帧所有的过程都被丢掉了。在只需要算账的年代这个压缩是可接受的但在需要跨部门协同、追踪履约、分析业务贡献的今天过程本身就是最重要的数据资产。3.2 经济交换与经济转换REA 模型用两种基本关系描述资源的状态变化。第一种是经济交换两个执行事件结对出现一方的资源流出去另一方的资源流进来买卖支付都属于交换。第二种是经济转换某种资源被消耗、加工、重组变成了另一种资源生产组装、服务执行都属于转换它描述的是企业内部的价值增值过程。理解这两种关系特别重要因为很多业务系统的核心逻辑都可以归结为一系列交换和转换的组合。库存模块做的是转换原料变成品销售模块做的是交换商品换资金供应链协同则是交换和转换的嵌套。我见过不少数据团队在设计指标口径时对收入成本毛利吵得不可开交其实往底层一看都是在交换和转换的边界上没对齐某一步到底是消耗了资源还是让渡了资源答案不同指标当然不同。3.3 三种核心关系决定表结构在 REA 里三要素之间主要通过三种关系连接资源与事件之间是存量-流量关系一个事件消耗、产出或接收了多少资源。事件与事件之间是交换/转换关系哪些事件配对构成完整的业务动作。事件与参与者之间是权责关系谁在这个事件里承担了什么角色。这三种关系不仅仅是 ER 图上的连线它们直接决定了表结构的设计资源表和事件表之间需要有流量明细表事件表之间需要有配对字段或配对表事件表里必须保留参与者维度。很多系统表结构混乱拆开来看其实是该拆的流量明细没拆、该连的事件配对没连、该留的参与者维度没留。3.4 事件是天然的事实表如果从数据分析的视角看 REA你会发现事件表几乎就是数仓模型里最理想的事实表它有时间、有度量、有关联的资源与参与者天然支持多维分析。对问题这个季度哪个客户的毛利贡献最高传统做法要从凭证数据反推业务REA 模型则是直接从事件事实表按参与者维度聚合查询路径短语义也更不容易产生歧义。这解释了为什么很多讲业财一体化、数据中台的文章最终都会绕回 REA因为它为业务事实给出了一套稳定的、不随组织架构变化的底层结构。组织会变、流程会改但只要业务本质还是资源通过事件在参与者之间流动REA 就依然有效。我在第五章会专门讲这种稳定性带来的落地优势。4. 从订单到回款一个销售业务片段的完整建模4.1 圈定业务片段建模之前先圈定单元。我习惯把一个可独立闭环的业务动作作为最小建模单元而不是把整个 ERP 一次性画完。下面以一个标准销售业务片段为例客户下订单仓库发货客户付款。公司角色是卖方。这个片段的核心问题是如何让系统准确回答我们答应卖什么、实际发了什么、实际收了多少钱。4.2 实例化三要素把三要素落到这个业务片段里要素实例说明资源SKU 商品卖方的库存资源发货时减少资源货币资金买方支付的货款到账后增加事件销售订单承诺事件计划交付的标的事件发货单执行事件商品实际流出事件收款单执行事件资金实际流入参与者客户买方接收商品、支付资金参与者销售员卖方内部发起订单的经办人参与者仓管员卖方发货执行人注意这里没有把应收账款直接建模成资源。应收/应付是后续核销阶段的会计视角REA 更倾向于先把发货和收款两个实际事件记清楚再在账务层推导应收关系。这个取舍在落地时非常重要后面专门的落坑章节里我会展开说。4.3 连接关系的推演过程现在开始画关系这个环节是建模的核心不能用外键名称替代真实的业务语义销售订单是一个承诺事件它与未来的发货事件和收款事件构成承诺-执行关联。订单记录的是计划发货和收款记录的是实际。发货事件消耗了商品资源收款事件增加了货币资金资源。资源变化要落到具体金额和数量。发货事件与收款事件构成一个经济交换的配对。一笔发货对应一笔或多笔收款这就是配对关系。参与者连线销售员发起销售订单仓管员执行发货客户接收商品并且支付资金。客户在发货-收款交换里同时是接收方和支付方。推演时我有个习惯把每一条关系都用一句人话声明出来。比如发货让库存里的商品减少 N 件同时让客户获得这 N 件商品的处置权。如果一句话说不清楚说明关系还没想透先不要建表。4.4 落成最小表结构基于上面的推演可以设计一组最小可用的表。为便于演示我采用简化字段-- 事件表销售订单承诺 CREATE TABLE sales_order ( order_id BIGINT PRIMARY KEY, order_no VARCHAR(32) UNIQUE, customer_id BIGINT, -- 参与者客户 seller_id BIGINT, -- 参与者销售员 status VARCHAR(16), -- 草稿/已确认/已完成 created_at TIMESTAMP ); -- 事件表发货执行 CREATE TABLE shipment ( shipment_id BIGINT PRIMARY KEY, shipment_no VARCHAR(32) UNIQUE, order_id BIGINT, -- 关联承诺事件 customer_id BIGINT, warehouse_keeper_id BIGINT, shipped_at TIMESTAMP ); -- 事件表收款执行 CREATE TABLE payment ( payment_id BIGINT PRIMARY KEY, payment_no VARCHAR(32) UNIQUE, customer_id BIGINT, amount DECIMAL(18, 2), received_at TIMESTAMP ); -- 事件-资源发货的资源流出明细 CREATE TABLE shipment_resource_flow ( flow_id BIGINT PRIMARY KEY, shipment_id BIGINT, resource_type VARCHAR(32), -- 商品/服务等 resource_id BIGINT, quantity DECIMAL(18, 3), unit_price DECIMAL(18, 2) ); -- 事件-资源收款的资源流入明细 CREATE TABLE payment_resource_flow ( flow_id BIGINT PRIMARY KEY, payment_id BIGINT, resource_type VARCHAR(32), resource_id BIGINT, amount DECIMAL(18, 2) ); -- 事件配对发货与收款的交换关系 CREATE TABLE exchange_pairs ( pair_id BIGINT PRIMARY KEY, shipment_id BIGINT, payment_id BIGINT, settlement_note VARCHAR(255) );这个结构里的关键点有三个。第一承诺事件订单与执行事件发货分离第二事件与资源的关联通过资源流量明细表完成而不是在主单里塞一个库存数量字段第三发货和收款通过交换配对表建立联系将来部分收款、分批回款都可以拆行表示。4.5 与传统凭证分录方案的对比同样一个业务片段传统方案大致是一张业务单据主表加一张分录明细表字段包含科目、借方金额、贷方金额。订单、发货、收款都被压成分录靠单据类型字段区分。跑报表时常见两种尴尬一是想看业务履约进度必须在分录表里反复 JOIN 业务单据号语义绕二是库存明细和财务往来被绑定在一起库存盘点和财务对账任何一边动数据另一边跟着抖。REA 方案的差别在于事件表就是事实资源流水表就是存量变化交换配对表就是业务归属。查询某客户的毛利贡献可以从发货资源流水按客户聚合再对比对应的资金流入整个过程不用经过任何科目编码业务人员也能看懂查询条件。财务核算视图则可以在 REA 之上单独生成作为一层结果视图而不是反过来把业务过程压扁成凭证。5. 落地过程中最容易翻车的四个设计细节5.1 订单状态机放哪承诺事件要不要留修订痕迹承诺事件最大的特点是可以变更。客户可能改数量、改交期甚至取消订单。如果把订单状态存在同一张表里直接 UPDATE 状态字段历史承诺就看不到了REA 强调的过程可还原就破功了。我的建议是把承诺状态变化单独建成一个状态历史表或追加事件日志主记录保留最新状态历史记录用于追溯。这个设计带来的额外收益是业务上需要回答这单被改过几次、每次改了什么时不再需要从审计日志里人肉翻。5.2 退货与退款逆事件怎么处理逆向流程是 REA 落地时最容易被绕晕的地方。注意不要用负数把退货和发货混在同一张表里。REA 的标准做法是把退货建模为一个独立的执行事件退货是商品的反向资源流动退款是资金的反向流动它们再组成一个新的交换配对。这样退货原因责任归属复检结果这些品控维度都能挂在独立事件上不会污染正向发货的统计。用生活类比一个人在会议室 A 作了一次演讲又在会议室 B 作了一次相同内容的演讲它们依然是两场会议而不是同一场会议打了负号。5.3 多组织、多币种带来的粒度问题REA 的三要素本身不限定组织边界但落地时要在资源与事件上区分记账主体。多公司场景下同一批货物从母公司仓库调拨到子公司在 REA 里不应该简单记成一条库存转移而应该建模为子事件资源流动明细的组合这样才能支持每个组织的独立核算又不破坏总体资源守恒。多币种也是同样逻辑资金资源要保留币种维度交换配对表里要记录结算汇率不要在金额字段里偷偷做汇率折算。这类问题早期不处理等报表要对齐的时候就非常痛苦。5.4 事件表不等于领域事件别直接照搬事件溯源近几年事件驱动架构流行很多团队一看到 REA 里有事件就以为可以把事件表当消息总线的领域事件用。两者确实有相似之处但定位不同。REA 的事件表是业务事实的持久化记录它关心的是某个业务动作发生后的确定性状态消息总线的领域事件关心的是系统之间如何异步协调。一个 REA 事件可能会触发多条消息多条消息也可能最终汇成一个 REA 事件。落地时我的做法是事件表作为最终一致性的落库点消息队列只承担通知职责不要让消息替代事件表成为唯一事实源。6. 什么时候该上 REA什么时候别硬套6.1 越复杂越划算业务追溯、业财一体化、数据中台如果你在做进销存、供应链协同、业财一体化或者要建一个面向全链路的数据中台REA 几乎是教科书级的底层框架。这类系统的共同特点是多个部门围绕同一批资源协作必须回答谁在什么时间对什么资源做了什么。REA 正好把这个问题的答案结构化地记录下来了。我见过不少团队在数据中台里花大量时间定义事实表和维度表其实从 REA 出发事实表天然就是事件表维度表天然就是资源和参与者口径冲突能少一半。6.2 小而准的记账工具就不需要也不是所有系统都应该上 REA。如果一个工具的定位就是把账记平、把凭证打印出来用户不关心过程追溯那 REA 模型带来的复杂度的确不值得。这种情况用传统的凭证-分录-科目模型反而干净利落。REA 不是要取代所有会计系统它是在你确实需要过程数据的前提下才真正发挥作用的框架。怕的是团队在不必要的地方为了模型先进硬套结果把简单的需求全部做重。6.3 判断标准你更看重结果还是过程我总结了一个一句话判断标准系统最核心的价值是算得平还是还原得清前者适合传统账务模型后者适合 REA。如果两者都要也没有问题——先用 REA 把过程和事实记录完整再在其上生成核算视图。很多做业财一体化的团队最终都会落在REA 事实层账务结果层的双层架构上这是我目前见过的最不容易跑偏的组合。7. 实战中很管用的三条经验三件事是我自己用了 REA 模型以后觉得最值得分享的。第一先从白纸上的关系图开始不要在第一次就急着定义表的字段。用人话把资源、事件、参与者之间的每一条连线说清楚再把连线翻译成外键和流量明细。字段永远可以被后来者补充但关系画错了返工成本是最高的。第二把一个完整业务片段作为建模单元而不是从整个系统的 ER 图开始。每个片段做完跑通一轮真实业务数据确认查询口径对上了再接着扩展下一个片段。这个过程很像搭积木片段之间的接口就是共用的资源和参与者。第三命名尽量用业务语言。事件表叫发货收货收款就比叫biz_event_type_03好理解得多。REA 最怕过度抽象一旦事件表和资源流量表被命名成泛化的流水表业务人员和技术人员很快就会出现理解断层。保持命名贴近业务数据模型才能真的变成团队的通用语言。我还想说一个更实际的体会凡是做过系统重构的人都知道最难的不是重写代码而是把散落在老系统里的隐性业务规则重新找出来。REA 模型此时是一个很好的梳理工具。你只需要问三个问题这里有什么资源在流动动作是什么事件谁在参与很多纠缠不清的业务逻辑会迅速显形。这也算是我坚持在团队里传播这套建模方法最重要的原因。