ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

REA模型:用资源-事件-代理思想重构业务数据建模

REA模型:用资源-事件-代理思想重构业务数据建模 做企业信息系统设计的朋友应该都遇到过这种场景业务部门拿着一张销售订单来跟你谈需求财务部门又拿着凭证、发票来提要求最后数据库里到底以谁为准往往要吵上几轮。我早年做一套进销存加财务一体化的改造时就卡在了这个矛盾的节骨眼上。后来接触了REA模型——也就是Resource-Event-Agent资源-事件-代理建模思路——才感觉自己把业务到数据的那层窗户纸彻底捅破了。REA不是一个前端框架也不是某个中间件的名字它是会计信息系统和数据库设计领域一个基础到容易被忽视的建模思想。它解决的是企业在业务数据记录上的“语义丢失”问题传统的借贷记账只留下结果过程被人为折叠进凭证里而业务流程本身——谁参与了、发生了什么、资源怎么变——往往在数据模型里找不到痕迹。这篇文章我就从REA的底层逻辑讲起结合我做销售、采购、库存这类场景建模的实操经验把它拆给你看。这套东西适合谁我觉得三类人最该看一是ERP或财务系统的数据架构师设计表结构时可以用来判断字段冗余和职责边界二是业务分析师和产品经理描述跨部门业务时能比泳道图更靠近业务本质三是刚接触数据库建模的同学REA可以作为“自然语言到表结构”的桥接工具。往下读之前你只需要记住一句话REA不是用来记账的而是用来把业务事实记录完整再按需生成各种账。1. 为什么很多系统设计到最后会“变形”1.1 记账科目背后的信息断层在绝大多数企业管理软件里数据模型的骨架其实是科目、凭证、单据这类会计元素构成的。先别急我不是说会计不对会计这套体系运转了几百年在合规和核算上非常成熟。但问题在于传统借贷记账是为“财务结果”服务的不是为“业务过程”服务的。销售订单、仓库发货、开票、收款在传统模型里被拆成销售单、出库单、应收单、收款单表面看每一环都有据可查可这些单据之间的联系是脆弱的。你通过外键能把销售单和出库单串起来但串起来之后能回答什么问题最多是“这张单对应哪张出库单”。如果业务想分析“某个客户的购买行为有没有因为信用额度收紧而发生变化”传统模型就答不上来。为什么因为信用额度的变化没有作为独立事件被记录它被折叠进了一张应收凭证的备注或调整分录里。换句话说会计记账作的是摘要是已经归类好的事实但面向经营的分析往往要从更细的过程事实中重新聚合。两条需求一冲突系统就只能不停地加字段、加冗余表、加各种自解释的“类型”字典最后表结构复杂到没有人能说清楚某个字段到底为什么存在。1.2 REA模型出现的背景REA模型最早是1982年由会计信息系统领域的学者提出的当初要解决的就是“记账模型丢失业务语义”的问题。它的核心主张相当激进与其把业务折叠成凭证不如把业务本身的事件、资源和参与者原样记录下来财务报表只是这些明细数据的一种聚合视角。这个思想在当年的存储条件下显得奢侈因为计算机硬盘还很贵少存冗余是最优先的工程诉求。但放到今天看它和数据中台、湖仓一体的思路几乎同源先沉淀真实业务过程再按不同的分析需求出不同的摘要。我当时是被一句话点醒的“传统记账是先做分类再做账REA是先记录事实再做分类。”两者都能产出财务报表但REA多保留了一层底层语义这层语义恰恰是现在做数据分析最需要的东西。你去看那些BI报表需求一大半问题都可以用“某个事件在什么时间发生、涉及了什么资源、由谁参与”来回答而这正好落在REA的三元组上。1.3 REA到底适不适合你我判断一套建模方法适不适合自己就看它能不能降低沟通成本。REA在以下场景里价值非常明显多业务线共用一套数据模型、财务和业务口径经常有分歧、需要从明细数据倒推各种自定义报表。反过来如果项目只是做一个部门级的简单登记工具没有跨主体、跨责任中心的协作需求那REA会显得“过度设计”。还要提醒一句REA不是银弹。它是分析业务语义的透镜不是工程实现的强制规范。实际项目里你完全可以只用它的分析框架去梳理业务但表结构仍按性能需要做物化或冗余。REA的价值在于告诉你“哪些字段是本质哪些字段是派生”至于要不要把派生字段存下来那是工程权衡不影响REA本身的正确性。理解这一点你就不至于被“纯正的REA”束缚住手脚。2. REA模型的三个核心元素与三类关系2.1 Resource不是所有数据都能叫资源在REA里Resource资源的门槛比直觉上严格。不是随便一张单据、一个Excel附件都算资源它必须同时满足两个关键特征稀缺性以及可控制与可交换。空气不算资源因为它取之不尽无法带来经济利益库存现金、银行存款、商品、原材料、设备、知识产权这些算资源因为它们既能被某个主体控制又能在主体间流转。建模时最容易犯的错误是把自己系统里的“订单”“合同”“发票”当作资源挂进模型。订单是事件的载体记录的是某个时间点某个代理做了什么决策合同是契约的载体描述的是未来要发生的事件集合。它们本身不能被出售、不能耗用所以不是资源它们承载的信息应该落在事件或关系上。这个判断准则很实用如果你发现某个“资源”实体无法回答“它被哪个事件消耗或产出”那它就大概率不是资源。我给资源实体加字段时建议多留一层“状态字段”比如商品的“在仓、待发、已锁定”款项的“未核销、已核销”。原因后面讲事件-资源关系时你会看到同一个资源被多个事件并发引用时没有状态控制查“可用库存”和“实际库存”会彻底对不上。2.2 Event用动作而不是状态记录业务Event事件是REA模型的中心元素。一个事件必须是一次具体的、发生在某个时间点的业务行为比如“提交订单”“发货”“收款”而不是一个状态。很多人建模时写“已发货”字段以为这就是发货事件其实“已发货”是发货导致的状态结果事件本身是“发出货”这个动作。事件的记录质量直接决定后面所有分析的精度。我的实用建议是每个事件至少保留三个时间字段——业务发生时间、系统录入时间、审批完成时间。这三个时间经常不一致而分析侧最喜欢问“发货时间和订单时间差几天”“到款比承诺晚几天”。如果把这三个时间揉成一个字段后面就只能靠各种间接日志去猜而日志常常是缺失的。在事件粒度之外还要养成给事件分类的习惯。我一般把事件分成两类承诺事件比如接收订单、安排采购和兑现事件比如发货、收款、收货。承诺与兑现之间的差恰好就是业务风险敞口比如未兑付订单、未收款订单。这个视角比“订单状态词典”更稳定因为状态词典会随业务调整不断膨胀而承诺-兑现的结构不会变。2.3 Agent参与者绑定责任链Agent代理是事件中的参与方分内部代理员工、部门和外部代理客户、供应商。一个完整的经济事件应该同时关联一个内部代理和一个外部代理。比如“销售发货”内部参与方是仓管员外部参与方是客户。如果某个事件只能找到内部操作人而找不到外部对象要么事件定义有问题要么业务描述漏了关键环节。这一点落在系统设计里直接导向职责分离和权限控制。很多系统里只有一个“操作人”字段但真实业务里“创建人”和“审核人”往往是不同的人。如果按Agent思路建模你会很自然地设计出“发起Agent”和“审批Agent”两个关联角色。这不仅是合规要求也是事故追溯的审计基础。我遇到过不止一次线上数据被误改、但系统里只能查到最后编辑人的尴尬最后只能靠数据库日志翻记录这种坑在设计阶段就能避免。2.4 三类关系双向因果是灵魂REA中实体之间的关系严格来说就三类资源-事件表示资源流入或流出事件-事件表示双向因果依赖事件-代理表示参与关系。其中最容易忽略的是事件-事件的“双向”语义。举个例子发货和收款之间不是一条单向箭头“发货→收款”而是双向依赖——发货产生了“客户欠款”的义务收款消解这个义务。以传统ER图表达时这个语义常常被淡化成一个外键关联。REA强调把它显性化为双向因果目的就是让你在设计时明确如果未来出现“货已发但款没收到”系统应该能通过事件关联自动暴露而不是靠人工对账。我把这三类关系浓缩成一句建模口诀“谁在什么时间通过什么事件动用了什么资源产生了什么增减。”你建模时把每个实体都往这个句子里套套不进去说明业务概念还没彻底想清楚。这个口诀我带了几个项目基本每次都能在需求讨论时最快地拉齐认知。3. 从一个销售模块落地看REA建模全过程3.1 场景描述销售发货与收款拿最经典的销售场景来实操。业务描述是客户A下了一张订单购买100件商品B单价100元仓库第二天发货财务三天后收到客户全款。我会带着你从头把这些信息转化成REA结构。先别去想着“我要设计哪些表”。第一步是讲清楚业务把“人、事、物”分离。这个场景里的人有客户、销售员、仓管员、财务事有接收订单、发货、收款物有商品B和现金。人天然是Agent事天然是Event物天然是Resource。这就是REA建模的第一步按语义分堆而不是按部门分堆。3.2 识别资源、事件、代理对着这个场景我通常会把结果写成一份清单资源商品B销售流出的资源银行存款收款流入的资源如果场景扩展为退货还要考虑“退货权”这类的非物质资源先不展开。事件接收客户订单承诺事件、销售发货兑现事件、收款兑现事件。代理客户外部代理、销售员内部代理、仓管员内部代理、财务内部代理。有人会问应收账款去哪了这是一个关键理解点。在REA中应收账款不是资源不是事件也不是代理。它是“发货事件”和“收款事件”之间未闭合的差额投影。这听起来比较绕但它带来的设计结果是好的你不需要在数据库里维护一张“应收余额”表而是可以通过“已发货未收款”的明细视图动态计算。这样应收数字永远与实际业务过程一一对应不会出现财务系统和业务系统对不上账的经典矛盾。3.3 逐步画出ER模型的过程实际画图我按三步走。第1步先画资源及最靠近它的事件。商品B被“销售发货”事件流出被“采购收货”事件流入扩展场景所以商品B与发货事件是“流出”关系与收货事件是“流入”关系。银行账户被“收款”事件流入被“付款”事件流出。这步先不碰代理。第2步画事件之间关系。“接收订单”与“发货”之间是因果依赖前者是承诺后者是兑现“发货”与“收款”之间是双向依赖对应义务的产生和消解。注意事件之间不画时间顺序箭头只画业务责任上的因果引用。第3步把代理挂上去。接收订单事件关联客户和销售员发货事件关联客户和仓管员收款事件关联客户和财务。三步完成之后一张REA图就成型了。它和流程图的区别在于图里没有“下一步”的推进感只有谁对谁负有义务、谁动了哪些资源的结构感。这种结构是稳定的流程可以调整、单据可以改版但“发货产生收款义务”这个因果关系不会轻易变。3.4 表结构映射与聚合报表把REA图落成数据库表我会按下列模式映射resource表resource_id, resource_type, name, status, quantity_unitevent表event_id, event_type, event_time, business_time, notesagent表agent_id, agent_type, agent_name, agent_deptevent_resource表event_id, resource_id, flow_direction, quantity, unit_price, amountevent_agent表event_id, agent_id, agent_roleevent_link表event_id_from, event_id_to, link_type这套结构的要点是事实明细都在event_resource和event_agent这两张关联表里其他表都是维度。后续做报表比如“按客户汇总欠款”其实就是在event_link里找到“发货事件→收款事件”的关联聚合发货金额减去已收金额。因为所有口径都建立在同一套明细上财务和业务不再各拿一张不一致的表来争论数据。这一点是我在真实项目中感受到REA最大价值的地方。4. 更完整的业务循环采购、库存与薪酬怎么复用REA4.1 支出采购循环的镜像结构销售循环学会了采购循环几乎是它的镜像。卖东西是先有“接收订单”承诺再有“发货”兑现最后“收款”买东西则是“提交采购申请”承诺“收货”兑现“付款”兑现。资源端是原材料或商品流入银行存款流出。代理端是供应商外部和采购员、仓管、财务内部。我在做采购模块时最大的一个收获是把“供应商”放进Agent体系而不是单独做成一张“供应商主数据”了事。因为REA要求每个收货事件必须关联一个外部代理你会自然地想到一个供应商可以有多个联系人、多个结算账户这些信息应该挂在Agent的扩展属性上。相比“供应商表”里堆一坨字段这种建模更干净也更符合真实业务里“一个供应商多个业务员多套收款信息”的需求。4.2 薪酬循环当资源是“人”时怎么办薪酬循环是个有意思的边界案例。员工提供劳动服务企业支付工资。这里劳动服务是一种资源但它是瞬时资源不能存储提供即消耗。这类资源的建模和库存商品不一样商品库存需要数量状态劳动服务只需要事件记录本身不需要库存记录。这给我们的启发是REA并不要求所有资源都带数量账。资源的状态设计取决于资源是否可存储。可存储资源要关心存量不可存储资源只关心流量。如果强行给劳动服务也做一套“剩余库存”建模就会非常滑稽。建模的时候多问一句“这个资源能被囤起来吗”能避免很多设计过度。工具、软件、咨询服务我都按这个逻辑判断是否有必要建库存表。4.3 用同一套模型收敛多业务线我曾在一个项目里同时做销售、采购、库存、薪酬四个模块一开始每个模块都各自设计了独立的表后来用REA重新梳理发现四套表在结构上高度同构都是Resource Event Agent外加两张关联表。最后我们做了一套通用的事件模型基座各业务线只是在“事件类型”“资源类型”上做了枚举扩展。这个方案带来的直接好处是报表口径统一了跨业务线的分析变成同一类表的聚合代码层面通用数据访问层只用写一遍业务扩展时新业务只需要新增枚举和校验规则不用新建一张张几乎重复的表。当然这也要求团队在职责分离和数据权限上投入额外设计否则通用模型会变成“谁都能写谁都能读”的混乱源头。如果你团队的数据模型能力还不够先别急着追求这种收敛从单个业务循环的REA化开始更稳妥。5. 实操避坑事件粒度、循环交错和工具选型5.1 事件粒度怎么判断建模时第一个容易纠结的问题事件粒度取到哪一级“提交订单”和“审核订单”要不要分成两个事件“仓库拣货”和“发货”呢我自己的判断标准是只有对资源增减或义务消解有直接影响的行为才单独建事件。审核、审批通常是对先前事件的确认和检查不是新的经济事项所以我不会把“审核”单独立事件而是在原事件上加状态和审核代理。否则一个简单的下单流程能拆出五六个事件事件链无限膨胀每个关系的语义反而被稀释。如果团队里有人提议“凡是系统里有的操作按钮都算事件”一定要警惕。事件不是操作日志操作日志记录的是人的动作事件记录的是资源与义务的变化。你可以把操作日志作为审计辅助但它不应该混入业务事件模型。这一点想清楚后续做数据清洗和历史追溯会轻松很多。5.2 循环交错与现实中的多对多另一个常见问题是“循环交错”两个事件之间因为业务规则存在多重关联。比如销售发货事件它关联了销售订单这个承诺事件而销售订单又可能关联“客户预算”或“采购备货”事件同时一笔发货可能对应多张订单一张订单一分多次发货。这种多对多关系在传统外键建模里很难直接表达我的经验是专门建中间关联表或关系表把链接类型、生效时间、数量都放在关系里而不是在事件表上加几对冗余外键。中间表还有一个好处可以承载数量分摊。发货10件对应两张订单每张订单多少数量在关系表上记录分摊明细比在事件表里用字符串硬存靠谱得多。记住一句话复杂多对多不是设计的错而是业务本身的复杂度关系表是容纳复杂度的安全垫。5.3 REA图和流程图是两回事把REA图画成流程图是我见过最常见的跑偏方式。很多人画事件用“上一事件下一事件”的箭头串起来最后造出一张没有数据语义的泳道图。REA中的事件关系是因果依赖或义务关联不是时间顺序。虽然多数业务是先发生A再发生B但模型关心的不是顺序而是互相牵制的业务义务。想表达流程顺序就单独画流程图、时序图不必混在REA图里。判断一张REA图是否合格我有个土办法拿掉所有“先/后”的时间箭头只留下事件关联后如果模型依然能表达业务规则那它是合格的数据模型如果图马上就散了说明你还是画了一张流程图。这个筛选方式在评审会上很好用能快速把跑偏的讨论拉回来。5.4 工具选型与轻量起步建模工具这块我反复踩过很多次坑最后发现最适合轻量起步的是白板和纸笔。实体加关系线一张A4纸正好铺下一个业务循环。等实体超过20个再迁移到专业的ER建模工具或共享白板画布。为什么我不推荐一开始就用重型建模工具因为REA建模的过程本质是业务讨论工具太复杂会把讨论带偏到“这个符号怎么画”上而真正需要被讨论的是事件的定义、粒度和资源归类。如果你已经有ER建模工具直接用就行REA图本身就是ER图的语义扩展只是在关系线上要额外标注是“流入”“流出”还是“因果依赖”。最重要的一点REA图永远为业务服务工具的漂亮程度远不如一张有争议、有注释、被人反复修改过的白板照片。6. 常见问题速查与排障经验6.1 五类典型建模错误速查表我整理了五类从初学者到老手都容易犯的典型错误做成一张速查表放在下面评审模型时对照着看非常高效。问题识别信号解决建议把单据当资源“订单表”“合同表”出现在资源位置区分事件载体与资源本身订单是事件记录事件粒度太细出现大量“审核事件”“查看事件”只保留影响资源增减或义务消解的事件事件关系画成顺序箭头事件间全是单向“下一步”箭头改成双向依赖或因果引用时间顺序交给流程图事件缺少外部代理每个事件只有内部操作人每个经济事件都补一个外部代理应收应付被实体化模型里出现“应收余额表”改为发货事件与收款事件之间动态聚合这张表不是理论推演是我自己在一个多租户SaaS项目踩过坑后总结的。当时最痛的一条就是“把订单当资源”导致商品库存明细里混入了一堆单据对象业务查询时不得不反复过滤类型字段后来重构花了整整两周。6.2 应收余额到底要不要落表总有人问我既然REA说应收账款是差额投影那我们的应收余额落表吗我的实际建议是日常流程表可以落冗余的应收余额方便财务做账龄、对账但必须明确它是“派生物”由事件明细定期重算。如果让应收余额成为唯一可信的数据源那业务过程发生修正比如退款后余额表又会产生一堆红字冲销审计追溯变得非常痛苦。我见过最好的做法是事件明细表为主应收余额作为物化视图每天定时重算。这样既能满足财务报表性能又能保证问题发生时你可以从余额一路追到明细事件。DB层面物化视图与明细表的一致性靠批处理保证业务层面一旦发现余额对不上第一反应去查事件明细有没有缺漏而不是直接改余额。这个习惯养成了账实相符率会高很多。6.3 建模交付物别只交一张图我参与过十几个项目最怕碰到只交付一张REA图就拍屁股走人的团队。REA图表达的是全局关系但它表达不了每个实体被定义成Event还是Resource的理由。所以我的交付清单里一定包含三件套实体关系图、实体字典、与现有业财报表的映射说明。实体字典特别关键里面要写清楚每个实体的含义、判断归类的原因、关键字段解释。为什么这么重要因为建模三个月后一定会有人来问“这个event_resource表为什么存在”“为什么没有收款状态字段”。有字典这些问题可以在五分钟内解答没字典就只能考古式地翻代码和聊天记录。我一般让负责业务梳理的分析师来维护这份字典因为他们在讨论中最清楚每个定义背后的业务权衡。讲到这里我想回到最开头那句话REA模型不是什么高深莫测的名词它只是把“业务到底是什么”重新拆成了资源、事件、代理三块积木。我个人在实际项目中体会最深的一点是用了REA之后财务和业务同事开会时不再各说各话——财务问“欠我们多少钱”业务答“已经发了哪些货、收了哪些款”。两边在描述同一件事只不过是同一套事实的不同聚合方式而已。如果你正准备设计新的ERP模块或者正在重构一套老掉牙的进销存数据结构我真心建议你先别急着建表。抽出半天时间把要做的业务用“资源-事件-代理”的三角框架捋一遍。这半天花下去后面改表的痛苦能省掉一大半。最后再多分享一个小技巧建模时多想一想“如果做数据分析的人拿到这套表能自助回答哪些问题”你会发现设计出来的表结构远比以前抗造。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进