REA模型:从资源-事件-参与者视角重构业务数据建模
看到“rea”这个标题的时候可能有人会以为是某个内部项目代号或者是某个工具缩写。但我今天想聊的是业务建模领域绕不开的一套经典语义模型——REA也就是 Resource-Event-Agent资源-事件-参与者。我最初接触到它是在做进销存系统重构的时候当时传统的借贷记账模型在财务核算上没什么问题但放到业务中台、流程追溯和数据仓库建设里就处处别扭后来换了 REA 这套视角很多纠缠不清的问题一下子变得清爽了。这套模型特别适合这几类读者正在做 ERP、供应链、订单中台或者会计信息系统设计的开发者想给数据仓库搭一套能支撑审计追溯的底层语义模型的数据工程师还有那些被“业务流程到底怎么建模”折磨的架构师。它不只是一个学术概念真正用起来之后你会发现它能直接指导表结构设计、状态机划分和接口语义定义。接下来的内容我会把 REA 的底层逻辑、建模步骤、落地到数据库的方法以及我自己踩过的坑一次讲清楚。1. 为什么一谈业务建模就先想到REA资源和账本背后的那套统一语言先说个背景。企业里的业务系统五花八门订单系统管单据库存系统管数量财务系统管借贷CRM 管客户关系。表面上各司其职但一旦要做集成、做跨系统对账、做数据仓库汇总问题就来了同一个业务动作在订单系统里叫“已发货”在库存系统里叫“出库”在财务系统里叫“主营业务成本结转”在物流系统里叫“运单揽收”。到底什么才是这件事的“真身”REA 模型最早是会计信息系统领域提出的思路特别朴素不管你们系统里用多少术语企业的经营本质就是一遍遍发生“事件”这些事件要么让某些“资源”增加要么让某些“资源”减少而且一定有人或组织在参与。你不需要纠结叫“发货”还是“出库”只需要承认一个事实有一件商品从仓库被转移出去了这是事件商品的库存数量下降这是资源被消耗操作人是仓管员这是参与者。为什么这个视角重要因为它把业务建模从“账户语义”解放到了“事实语义”。传统借贷记账法是以“账户”为中心的借什么、贷什么、记在哪个科目下这套规则是给财务人员看的它经过了非常高度的抽象和编码。而 REA 是以“经济事件”为中心的更贴近业务现场的真实动作。财务上你可以把“发货”和“收入确认”放在两个科目但业务事实其实是同一个连续过程的两端。用 REA 建模你记录的是“发生了什么”而不是“这个动作在账本上叫什么”。我实际用下来的体会是REA 特别适合作为企业数据中台的“公共语义层”。数据中台最怕的就是各个来源系统的字段各说各话REA 给了你一套稳定的实体骨架资源、事件、参与者以及它们之间的关系。任何业务域都能映射到这三大类上你只需要在细节层面做扩展而不是每接一个系统就重新发明一套模型。比如销售域资源是商品和现金事件是销售和收款参与者是客户、销售员、财务采购域资源是商品和现金事件是采购和付款参与者是供应商、采购员、财务。骨架完全一致这就是它的通用性。前阵子我接手一个库存与财务对账项目业务方一直要求“保证账实相符”但两边经常对不上。传统做法是大量调账和手工解释。我们花了大概一周时间把几套系统的核心动作全部用 REA 重新梳理成事件流发现很多对不上的地方根本不是数据错误而是系统间对“事件发生时点”的定义不一样一个系统在开单时扣库存另一个在发货时确认成本。问题本质是建模视角不统一而不是计算逻辑有问题。后来约定了一套标准事件口径对账问题一下子少了七成。2. 三大构件和四条关系REA本体模型的核心拆解要真正把 REA 用起来不能只知道缩写得把它的实体和关系吃透。这一节我会结合生活化的例子把核心内容讲清楚因为这些东西直接决定你后面表怎么建、关联怎么落。2.1 三大构件经济资源、经济事件、经济参与者REA 的第一个核心实体是经济资源Resource指企业拥有或控制、具有经济价值的对象。它不限于实物商品现金、应收账款、服务能力、软件许可证、积分资产都算。在建模时判断一个对象是不是资源就一个问题它能不能被一个经济事件增加或减少比如“商品库存”能被入库增加、出库减少是资源“订单状态”不能被某个资源的增减直接表达那它就不是资源而是业务状态。第二个核心实体是经济事件Event这是整个模型的发动机。经济事件是让经济资源的价值发生变化的活动比如销售、采购、支付、收货、领用、退货。事件具备两个关键特征一是发生在某个时间点二是有明确的数量和金额信息可以落到某个资源上。你在建模时脑海里要有一个公式在转每个事件一定有一个主资源以及一个数量或者金额的变化方向是增加还是减少。第三个核心实体是经济参与者Agent指参与事件的人或组织分为内部参与者员工、部门、仓库和外部参与者客户、供应商、承运商。参与者回答“这件事是谁做的、和谁做的”这一审计问题。很多团队建模时只关心资源和事件把操作人、往来单位当成普通字段塞在事件表里这在 REA 视角下是巨大的浪费因为参与者是独立核心实体它要和事件建立正式关系才能支撑权责追溯。2.2 四条关系存量-流量、控制、职责、触发光有三个实体还不够REA 模型真正有价值的是定义了四条标准关系。第一对是存量-流量关系也就是资源与事件之间的关系。资源是存量事件是流量一个资源可以被多个事件影响每个事件恰好作用于至少一个资源。举例来说“咖啡豆库存”是资源“采购入库”和“制作出杯”两个事件都直接改变它的数量。这条关系最重要的启示是资源永远不会平白无故地变它一定是由事件驱动的。你在设计 Redis 缓存或库存表时如果看到数量被直接 set 一个值没有任何事件来源那就要警惕了。第二对是参与关系也就是事件与参与者之间的关系。这是最简单的多对多一个销售事件有客户参与同时也有销售员参与一个采购事件有供应商参与也有采购员参与。注意参与关系应该落在“事件-参与者”上而不是“资源-参与者”上。很多系统里把“客户-商品”直接建关联这是不合理的客户不是和商品直接产生关系而是通过购买事件和商品产生法律关系。第三对是控制关系表示参与者可以支配哪些资源。比如仓管员控制着仓库库存出纳控制着现金账户。控制关系往往成为权限系统的语义来源你有权操作某类资源意味着你有权参与某些事件。这个关系在企业建模中容易被忽略等到做审批流、做数据权限时才发现到处补洞。第四对是触发关系表示事件与事件之间的因果或时序联系。比如销售事件触发收款事件采购事件触发付款事件发货事件触发物流事件。触发关系把独立的事件串成了完整的经营链条这是 REA 能支撑流程追溯和审计的关键。在建模时你需要明确每个事件的前置事件和后置事件形成可回放的事件流。这几个关系听起来简单但真正搭建模型时你能一直靠它们来校准设计。遇到不确定的字段归属就问一句这是在描述资源、事件还是参与者又是在描述四类关系中的哪一种答案明确模型就不会跑偏。3. 用一个电商出库流程完整走一遍REA建模理论部分说完了咱们直接上手。我会用一套大多数人都接触过的电商销售出库流程做例子带你从业务描述一步步推演出 REA 模型。这个过程和我实际工作中做业务域梳理的步骤完全一致。3.1 第一步圈定业务流程边界与目标我们建模的范围是从“顾客下单”到“确认收货并完成收款”。如果范围再大就会扯进营销、售后、供应商补货模型复杂度会失控。第一次建模的人最容易犯的错就是贪大求全我建议宁可先截取一个完整闭环比如一笔订单从产生到回款把这个闭环跑通、跑顺再往外扩展。这个流程涉及的角色包括顾客、客服、仓管员、财务人员、物流公司。涉及的资源对象包括可售商品库存、现金账户、应收账款。涉及的动作包括顾客提交订单、仓库发货出库、物流送达顾客签收、财务确认收款。这里有一个容易混淆的地方“顾客提交订单”算不算 REA 意义上的经济事件严格看它本身还没有让资源价值发生变化更像是一个承诺或者业务意图。所以更合理的建模是把它作为流程的起点状态而真正的第一个经济事件是“销售确认”或者“发货出库”。3.2 第二步逐事件识别资源增减与参与者责任我们一个一个事件来对照。第一个关键事件是销售出库操作角色是仓管员外部参与是顾客所属订单资源影响是“可售商品库存”减少同时产生一笔“应收账款”资源的增加。对这里的应收账款不是凭空出现的它是由销售事件触发的债权性资源。第二个关键事件是物流送达并签收参与者是物流公司和顾客资源影响是“在途商品”减少、“已交付商品”或者直接说履约完成同时“应收账款”仍然挂账。第三个关键事件是确认收款参与者是财务和顾客资源影响是“应收账款”减少、“现金账户”增加。这里要注意我把同一个业务动作拆成三个事件而不是揉成一个“订单完成”大事件。原因是每个事件发生的时点不同、操作的参与者不同、影响的资源不同。这道边界切得准不准决定了后面做分环节统计和审计时能不能拿到细粒度数据。细粒度数据和汇总数据并存没问题但反过来从粗粒度倒推细粒度就难了。为了便于大家理解我把这个流程的 REA 建模结果整理成一张表经济事件前置触发事件影响的资源方向内部参与者外部参与者销售出库订单提交商品库存减少、应收账款增加仓管员顾客物流签收销售出库在途商品减少、履约进度完成—物流公司、顾客确认收款物流签收应收账款减少、现金账户增加财务人员顾客这张表就是整个模型的核心骨架。你可能发现这里少了很多传统订单系统里的字段比如订单编号、商品名称、单价、税率。这些不是不重要而是在 REA 视角下它们是“事件发生时的快照信息”应该挂在事件记录下面而不是单独成为建模主体。商品名称挂在商品资源上单价挂在“销售出库事件-商品资源”的关联记录里订单编号则是事件链的分组标识。3.3 第三步校验模型能否回答业务问题模型搭完之后我们要做一次校验拿业务上真实关心的几个问题来质问模型。第一个问题“这个月卖了多少商品、卖了多少钱”答案在“销售出库”事件里按事件时间过滤、按商品资源聚合即可。第二个问题“有多少应收账款还没收回来”答案是把“销售出库事件产生的应收账款增加”和“确认收款事件对应的应收账款减少”做差。第三个问题“某个仓管员经手了多少出库订单”答案在“事件-参与者”关联里按内部参与者过滤即可。你会发现这些问题刨根问底到最后都落到了第 2 节说的四类关系上。这就是 REA 建模给人的底气和安全感你不是在依据报表口径凑数而是在依据业务事实回答问题。报表只是查询模型才是地基。4. 从概念到数据库REA模型的表设计与关联落地概念模型捋清楚之后最现实的问题就是怎么落到数据库里我见过不少团队REA 讲得头头是道一打开建表脚本又回到了老一套商品、订单、支付各建各的表关系稀里糊涂。这一节我给出一套可落地的表结构设计通用范式并说明每张表的职责边界。4.1 通用基础表事件表、资源表、参与者表如果是在做通用业务中台我会建议先建三张主表。第一张是经济事件主表economic_event承载所有事件的基本信息字段包括事件ID、事件类型、事件时间、全局事务ID、来源系统标识、备注。事件类型用字典或者枚举比如 SALE_OUT、CASH_RECEIPT、PURCHASE_IN。第二张是经济资源主表economic_resource字段包括资源ID、资源分类、资源编码、计量单位、当前存量、资源状态。第三张是经济参与者主表economic_agent字段包括参与者ID、参与者类型内部/外部、名称、证件号或统一信用代码。这三张主表是最顶层的主数据。实际业务中会有大量衍生的子类型表比如商品资源表、现金账户表、客户表、供应商表都分别通过外键挂到主表上。这样既保证公共模型统一又保留业务的差异化扩展能力。我用这套方案调整过好几个系统最直接的感受是后续接新业务域的时候不需要再推翻主表只需要新增子类型表。4.2 关联表事件资源表、事件参与表、事件链路表真正让 REA 活起来的是三张关联表也是很多设计师容易省略的地方。第一张是事件资源关联表event_resource记录每个事件对每个资源的影响明细。核心字段包括关联ID、事件ID、资源ID、变动方向增加/减少、变动数量、变动金额、业务发生单价。这里要特别注意金额和单价是“快照”必须当场记录不能事后去查当前价格因为供应链场景里同一商品在不同批次采购时价格可能不同你要还原的是事件发生时的口径。第二张是事件参与者关联表event_agent记录每个事件的参与者。一个事件可以关联多个参与者字段包括关联ID、事件ID、参与者ID、参与角色。这里的角色是语义化的比如“操作人”“审批人”“往来单位”。不直接把参与者ID做在外键上而是放进关联表是为了支持一个事件有多个相关方的现实情况也方便后续做权限或责任追溯时灵活查询。第三张是事件触发关系表event_link记录事件之间的指向关系字段包括关联ID、前置事件ID、后置事件ID、触发类型。触发类型可以是“直接驱动”“时间约束”“凭证引用”。有些场景里后置事件不一定马上发生比如销售出库之后可能一个月才收款所以表里最好带上期望间隔时间或者计划触发时间方便做流程预警。下面我用简化的 DDL 示意这几张表的关联要点CREATE TABLE economic_event ( event_id BIGINT PRIMARY KEY, event_type VARCHAR(64) NOT NULL, event_time DATETIME NOT NULL, source_system VARCHAR(64), remark VARCHAR(512) ); CREATE TABLE event_resource ( id BIGINT PRIMARY KEY, event_id BIGINT NOT NULL, resource_id BIGINT NOT NULL, change_direction TINYINT NOT NULL COMMENT 1-增加2-减少, change_qty DECIMAL(18, 4) NOT NULL, change_amount DECIMAL(18, 2) NOT NULL, unit_price DECIMAL(18, 4), INDEX idx_event (event_id), INDEX idx_resource (resource_id) ); CREATE TABLE event_agent ( id BIGINT PRIMARY KEY, event_id BIGINT NOT NULL, agent_id BIGINT NOT NULL, role_type VARCHAR(64) NOT NULL, INDEX idx_agent (agent_id) ); CREATE TABLE event_link ( id BIGINT PRIMARY KEY, prev_event_id BIGINT NOT NULL, next_event_id BIGINT NOT NULL, trigger_type VARCHAR(32) NOT NULL, INDEX idx_prev (prev_event_id), INDEX idx_next (next_event_id) );4.3 状态与快照如何在事件流上支撑查询性能看到这里很多人会问如果所有业务数据都走事件表查询当前库存、当前客户余额这类频繁高频的读取怎么办总不能每次跑一遍事件汇总吧答案是在事件流之上建立状态投影表。比如商品库存表可以保留一个冗余字段 current_qty由事件驱动的汇总任务异步更新。订单聚合表也可以保留当前状态字段由事件表驱动状态机迁移。状态投影表不是 REA 模型的破坏而是它在工程上的必要补充。事件表是事实的源头投影表是面向查询的缓存。二者通过一个不可回滚的“事件版本号”或“全局事件ID”保持一致。用数据库事务或者消息队列把事件消费到投影表的时候一定要遵循先写事件表、再改状态的顺序否则一旦失败就会出现状态和事件对不上的情况。我在实践中的经验是把事件表当作账本只准追加、不准修改删除把投影表当作草稿纸随时可以由账本重算。想明白这一点很多数据一致性的争论就消停了。5. 几个容易翻车的建模细节与我的避坑记录REA 在概念上不难难的是建模过程中那些“好像对、实则错”的细节。下面这几条都是我在项目里实际踩过的坑每一条都对应过一段难受的返工经历。5.1 把状态当成事件事件流被污染最典型的问题是把“订单状态变更”当成经济事件来建模。比如有人会给 REA 事件表单独增加“状态变更”事件类型把订单从待发货改成已发货记录成一个事件。这看着合理实际上把两类东西混在一起了状态是业务对象的属性投影而事件是资源增减的驱动源。状态变更应该作为事件发生的结果来描述而不是作为一个独立的经济事件。举个例子仓库完成“销售出库”这个经济事件之后订单状态自然会变成“已发货”。状态是被推导出来的而不是另一个事件。如果强行增加“状态变更”事件后面做对账和追溯时会产生二义性到底是先有状态事件还是先有出库事件数据口径会变得非常混乱。我建议在实现时把状态机只放在投影表上所有状态的迁移必须有来源事件ID作为依据不允许通过接口直接改状态字段。5.2 硬化资源与软性资源的计量单位不统一商品库存可以用件、箱、吨、个计量现金账户用元应收账款用元积分资源用个。这些资源在 REA 模型里都会走 event_resource 关联表如果change_qty字段直接用业务单位跨资源聚合会比较吃力。我的做法是在资源主表里增加一个“标准计量单位”字段同时把change_qty统一折算为标准单位数量另存一份change_qty_original保留原始业务单位数量。比如一件商品一箱装 12 个标准单位用“个”但仓库操作的时候扫描的是“箱”事件记录中原始数量是 2标准数量是 24这样汇总报表时直接按标准数量求和即可不会出现数量单位错乱的问题。这个坑在项目上线后暴露时修起来远比建模阶段多花数倍精力。5.3 事件与参与者关系上一对一思维顽固很多企微、审批流场景里业务天然的“一个事件只有一个操作人”思维会渗透到模型设计里。比如销售出库事件建表时直接放了一个operator_id字段后来业务要求同时记录“审核人”“拣货人”“复核人”时只能不断加字段一次迭代加一个。这是典型的参与者关系没建模好。正确的做法是第 4 节说的 event_agent 关联表参与者是一组而不是单一字段。后续如果想扩展“客户经理”“质量抽检人”直接插入关联记录不需要改表结构。设计阶段多做一个关联表后续能省掉 N 次数据库迁移。这是我在项目里最有体感的“设计救后期”案例之一。5.4 只建增量不见减量资源账对不平REA 事件是对资源增减的驱动但很多初版系统只记录了增加资源的动作比如采购入库、收款对于消耗或减少资源的动作没有建模成事件。最典型的场景是库存“报损”和“盘点差异”业务上本来应该是一个减少库存的经济事件但为了省事系统直接改库存表的数值。结果就是资源表里数量对不上盘点差异说不清来源财务审计要求逐一追溯每个差异时无从下手。正确的道路是任何数量的变动都必须具备对应事件哪怕盘亏也要建一个“库存盘亏”事件注明责任人、审批单、原因。只有坚持“资源无事件不变”这条铁律模型才能经得起审计。5.5 事件时间与业务时间的混淆事件表里我建议至少设计四个时间字段事件发生时间业务现场的真实动作时间、系统记录时间数据写入数据库的时间、业务归属期间比如属于哪个账期或月份、计划触发时间如果是前置计划类事件。很多团队只保留一个create_time后果是财务月结时上月底已经发货但本月初才录入系统的单子归属哪一期成了争议。REA 里事件时间应当由业务动作决定不能由数据库的落库时间决定。没有这个意识月末对账永远有一批“差异单据”要人工解释。最后再分享一个我一直在用的检查方法每建完一个 REA 模型我会找业务方随机挑一个资源然后从当前存量出发一路追问导致它变化的全部事件直到能够完全还原这个数字的由来。如果中间有任何一段说不清楚或者要借助系统外的手工表格说明这个模型还有缺口。建模本质上是在给企业的经营数字打地基地基稳了上面的报表、指标、决策才不会歪。