资讯详情

资讯详情

REA模型实战:用资源-事件-代理重构业务建模

1. 为什么我建议你认真学一下 REA 模型先说说我为什么会盯上“REA”这三个字母。如果你做过几年系统设计或业务建模大概率见过类似场景需求评审会上业务方滔滔不绝讲下单、退款、库存、对账会后你打开数据库设计工具却不知道该先画哪张表、哪些字段该归谁。传统思维是“先想想有哪些实体”但实体怎么定义、实体之间是什么关系往往拍脑袋居多。等到开发到一半才发现订单表和支付单表到底谁依赖谁、发货记录算不算状态变更这些基础问题可能还没扯清楚。REA 就是来解决这个问题的。它由“Resource、Event、Agent”三个词的缩写组成翻译过来是“资源、事件、代理”。早先被用作企业信息系统里的一套领域建模方法后来逐渐被更多人用在电商、财务、供应链、甚至内部审批流程的建模中。它不画流程图不画类图而是逼你先按业务本身的样子拆出三类基本要素再把它们之间的关系理成一套可验证的规则。用一句话总结就是把业务描述成“某个代理在某个时间通过某个事件获取或者转移资源”。这篇内容适合谁适合正在做业务系统建模的开发者、刚接触领域驱动设计的同学也适合那些觉得“实体关系图画得很乱”的架构师。我会结合一个典型的电商订单案例把 REA 的核心概念、建模步骤、易踩的坑一起讲清楚你最后可以照着思路直接套到自己项目里。2. 内容整体设计与思路拆解2.1 REA 到底在强调什么我们在做业务建模时最常犯的毛病是“名词先入为主”。比如做订单系统脱口而出就是“我有一张订单表、一张订单明细表、一张商品表、一张用户表”。这种思考方式默认了“订单”和“商品”是核心对象但事实上“订单”是业务过程产生的结果不是业务本身“商品”只是被交易的对象。REA 的思路彻底换了个视角不要先问有哪些表而是先问业务里发生了哪些事、谁参与了、事涉及什么对象。打个比方。传统建模像是在拍一张静态照片把此刻的库存、客户、商品都拍进去REA 则更像在看监控录像重点记录“哪个客户在什么时候买了多少商品”。照片只能说明状态录像才能说明变化。业务系统真正要支撑的不是记录“现在是什么”而是记录“发生了什么”以及“为什么发生”。这也是 REA 中“事件”被放在核心位置的原因。事件指业务中实际发生的、影响资源增减的动作比如“销售订单录入”“发货出库”“收款到账”。代理是参与事件的主体可能是内部员工、外部客户、供应商也可以是系统本身。资源则是有价值、能被权利主体拥有或控制的东西比如商品、现金、服务工时。这三类要素在业务中可以组合出完全不同的模型。2.2 它和传统 ER 模型、流程图有什么不同很多人会把 REA 当成另一种画关系图的工具其实不对。ER 模型描述的是静态实体及其关系它回答“系统里保存哪些数据”流程图描述的是流程的先后顺序它回答“事情按什么顺序发生”。REA 回答的却是更深一层的问题每次业务变动资源从哪里来、到哪里去、由谁操控、什么时候发生。举个例子。传统 ER 模型里你会画“客户”和“订单”之间的一对多关系理由是“一个客户可以下多个订单”。但 REA 不这么做它会把“客户下订单”视为一个事件客户是代理订单并不是简单的实体而是事件形成的记录。更进一步订单事件会产生“商品减少”“应收款增加”这类资源变化。这样建模后续做财务、库存、审计就很顺滑。REA 和流程图的区别也很好理解。流程图关注“下一步做什么”而 REA 关注“这个动作触发了哪些资源变化”。比如一笔销售流程流程图会画“受理订单→确认库存→发货→收款”REA 则会拆成几个独立事件销售事件、发运事件、收款事件。每个事件都有对应的代理和资源。看似更复杂但好处是模块化每个事件可以独立演进、独立入账、独立追溯。2.3 这套模型的优势业务口径统一我实际用下来的最大感受是REA 能让不同角色用同一套语言对话。业务方说“我们收到了货”“我们已经付款了”开发方不再需要反复确认“这是订单状态还是结算状态”。因为只要按 REA 建好模型任何业务变动都能落到“代理事件资源”这个固定结构上。还有一个好处是便于做数据追溯。之前造过一个内部项目要给一个跨平台营销系统做数据对账。用传统表结构对账逻辑散落在多个存储过程里改了又改后来按 REA 重新梳理把每个营销动作都当成事件资源和代理都挂了编号对账就变成“看哪些事件缺少配对资源”清晰很多。所以 REA 不只是理论它是能切实改善系统的。3. 核心细节解析与实操要点3.1 资源不是所有数据都叫资源在 REA 模型里“资源”的定义有严格边界必须是有价值的、稀缺的、可以被某个代理控制的对象。比如商品、货币、原材料、服务工时、专利、积分都可以算资源。但“订单编号”“物流单号”这类纯标识性信息不算资源它们更像是事件的记录元数据不产生增减。另一个容易混淆的是“资源”和“账本条目”。比如数据库里存了一条“库存数量为 10”的记录这不是事件这是某个时间点的资源快照。REA 关注的是资源的变化历史而不是某一瞬间的存量。如果你拍脑袋把“库存表”当成核心那库存是怎么变来的看不出来。但如果你把“采购入库”“销售出库”“退货入库”都建模成事件库存自然就是这些事件结果的累计。实操时我建议你先列一个资源清单对每一项问三个问题它有价值吗它能被拥有或转让吗它会被消耗、增加或转化吗如果三个答案都为“是”那它才是资源。否则它可能是事件属性也可能是某个代理的信息。3.2 事件业务行为的原子化事件是 REA 的灵魂。它不是用户点击某个按钮的前端动作而是业务域里真正有意义的经济活动。判断标准是这个动作是否造成至少一个资源的增加或减少并且能够独立核算、独立追踪。比如“客户提交订单”会引起“商品减少”和“应收款增加”所以是事件。但“订单状态从待支付变成已支付”本身不算新事件真正的事件是“支付成功”它导致现金或第三方支付账户资产增加、应收款减少。事件一定要是原子性的不要再拆分出更小的业务含义。你可以把“支付成功”看成不可再分的业务事实它要么发生要么不发生。原子化这个词听起来抽象我举个例子。你做一个退款流程可能系统里分了“退款申请”“退款审批”“退款打款”三个步骤。但按 REA 来分至少有两个独立事件第一个是“客户发起退款申请”这不影响资源第二个是“退款打款成功”这才减少现金或应付账。如果你把两个步骤硬拼成一个事件后续统计“今天实际退了多少款”就只能翻各个申请单的状态相当痛苦。3.3 代理注意内部代理和外部代理代理就是参与事件的个人或组织它分内部代理与外部代理。内部代理是组织内部的员工、部门比如“销售员”“仓管员”外部代理是客户、供应商、合作伙伴、政府机构等。区分内外代理在权限设计、职责分离场景下非常重要。比如“销售事件”由外部代理“客户”发起但由内部代理“销售员”承接可能还涉及审批的“主管”。同一张事件记录里可以同时挂多个代理但必须明确每个代理的角色。实操建议是不要为“系统”本身设置成代理除非系统是自动执行动作的。比如“自动发送催付短信”不算业务事件因为它没有改变资源。但如果“系统管理员手动取消了一笔错误订单”管理员是代理取消动作影响资源那这个事件就要记录。建模时把代理字段做成外键不要当作普通字符串这样后续才能按代理维度做报表。3.4 关系规则经济事件的成对性REA 模型中有一个容易被忽略但极其重要的规则一个导致资源流出的经济事件必然存在一个导致资源流入的对应经济事件。这就是“成对性”。例如销售出库资源流出必然对应客户付款或应收账款增加资源流入虽然应收账款不是立即现金但它代表债权资源。采购入库资源流入必然对应现金流出或应付账款增加。收付款事件则对应现金流入流出。这套规则听起来像会计准则但它本质上是告诉你不要孤立地看待业务动作。如果发现只建模了一个方向的资源变化另一个方向消失了那要么是业务本身没有闭环要么是你漏掉了事件。我在建模一个模拟项目X时一开始只建了“发货”事件结果库存减少了但收入始终对不上后来才补上“应收结算”事件整个账才平上。这是 REA 给自己做校验天然的优势。4. 实操过程与核心环节实现这次我用一个已经拆掉具体业务背景的模拟项目X来演示一个销售实物商品的订货发货系统。假设业务描述如下客户可以下单购买商品系统需要记录订单信息。仓库收到订单后发货并减少对应库存。客户收到货后可以确认收货。客户可以申请退款退款审核通过后原路退回款项。需要能够统计某段时间的销售收入、成本、库存变化。这些描述是很多企业业务的通用框架下面我一步步把它转成 REA 模型。4.1 第一步列出关键事件我习惯先把业务描述中所有动词和动作列出来再筛选真正符合“资源增减”条件的事件。业务描述候选事件是否算事件客户下单购买商品销售订单事件算导致商品资源减少、应收款增加仓库发货并减少库存发货事件算导致库存商品减少客户确认收货确认收货事件不算没有资源增减只是状态同步客户申请退款退款申请事件不算没有资源增减审核通过后退款退款支付事件算导致现金或支付账户减少原路退回款项与退款支付事件合并已在上面筛选后关键事件只有三个销售订单事件、发货事件、退款支付事件。确认收货和退款申请都只是流程辅助节点不产生真正的资源流转所以不进入核心模型最多作为独立状态记录。4.2 第二步明确每个事件的资源变动现在把三个事件涉及的资源变化写清楚销售订单事件不可能凭空让商品减少也没有立即收到钱所以这个事件会导致“存货资源”减少同时“应收账款资源”增加。需要注意的是实际业务中销售订单刚生成时仓库还没发货商品所有权未必立即转移。这里我按“订单确认并锁定库存”的场景建模假设下单即锁定库存、减少可售库存。如果你业务中库存扣减在发货时发生那“销售订单事件”可能只挂应收款增加而库存减少挂在“发货事件”上。不同方案都可以关键是要跟业务口径保持一致。发货事件导致“可售库存资源”减少同时确认“销售成本”的结转。更准确地说库存商品减少主营业务成本增加。但成本本身不是资源成本是计量口径所以在 REA 模型中发货事件通常关联“库存资源”的减少同时把成本信息作为事件属性存储。退款支付事件导致“现金资源”减少同时“应收账款资源”同步减少。注意这里要抵消原先销售订单事件产生的应收账款否则账不平。于是资源变动表长这样事件流入资源流出资源销售订单事件应收账款库存商品发货事件销售成本计量属性库存商品退款支付事件取消销售订单产生的应收减少现金/支付账户4.3 第三步确定代理及相关角色这一步要明确事件由谁发起、由谁办理。销售订单事件外部代理为客户内部代理为销售员或系统记录操作人。发货事件外部代理为客户作为收货人内部代理为仓管员。退款支付事件外部代理为客户收款方内部代理为财务审核人。实际建表时内部代理可能是一个“员工”表外部代理是一个“客户”表或“供应商”表。建议不要用同一个“用户”表来混装因为同一个身份证号的人可能既是员工又来买商品角色不同权限不同。代理表和角色分开比较稳妥。4.4 第四步补全事件间的关联关系REA 模型里事件之间可以有因果、时序、配对关系。比如销售订单事件会导致发货事件发生而发货事件又触发退款支付事件在某些取消场景。事件之间用“前因事件”与“后果事件”来关联。这里有一个关键点多个事件可以组成一个业务流程链但每一个事件仍然独立记录。比如销售订单事件和退款支付事件如果退款是因为客户取消订单它们之间就形成“销售订单事件 → 退款支付事件”的关系。这种关系在数据库中可以用一张“事件关联表”存储字段是“前序事件ID”和“后序事件ID”以及关联类型。这样做的好处是流程可以灵活调整不用把多个动作做成一个大事务。4.5 第五步转化为数据模型上面确定的东西最终要落地为表。我给一个简化但直接的建表示例-- 资源表 CREATE TABLE res_catalog ( id BIGINT PRIMARY KEY, res_code VARCHAR(32) NOT NULL, res_name VARCHAR(128) NOT NULL, res_type VARCHAR(16) NOT NULL -- INVENTORY, CASH, RECEIVABLE ); -- 代理表 CREATE TABLE agent_info ( id BIGINT PRIMARY KEY, agent_code VARCHAR(32) NOT NULL, agent_name VARCHAR(128) NOT NULL, agent_type VARCHAR(16) NOT NULL -- CUSTOMER, SUPPLIER, EMPLOYEE ); -- 事件表 CREATE TABLE business_event ( id BIGINT PRIMARY KEY, event_code VARCHAR(32) NOT NULL, event_type VARCHAR(32) NOT NULL, -- SALE_ORDER, SHIPMENT, REFUND agent_id BIGINT NOT NULL, counterparty_agent_id BIGINT, event_time DATETIME NOT NULL, -- 其他业务属性如订单号、支付流水号 ref_no VARCHAR(64), FOREIGN KEY (agent_id) REFERENCES agent_info(id) ); -- 事件与资源变动明细表 CREATE TABLE event_resource_item ( id BIGINT PRIMARY KEY, event_id BIGINT NOT NULL, res_id BIGINT NOT NULL, direction TINYINT NOT NULL, -- 1表示流入-1表示流出 quantity DECIMAL(18, 4) NOT NULL, unit_price DECIMAL(18, 4), amount DECIMAL(18, 4), FOREIGN KEY (event_id) REFERENCES business_event(id), FOREIGN KEY (res_id) REFERENCES res_catalog(id) ); -- 事件关联表 CREATE TABLE event_relation ( id BIGINT PRIMARY KEY, from_event_id BIGINT NOT NULL, to_event_id BIGINT NOT NULL, relation_type VARCHAR(16) NOT NULL, -- CAUSES, PRECEDES FOREIGN KEY (from_event_id) REFERENCES business_event(id), FOREIGN KEY (to_event_id) REFERENCES business_event(id) );这几张表就构成了一个非常通用的 REA 数据底座。你可以直接套到别的业务上无非是增加不同的事件类型、资源类型和代理类型。库存变化不需要单独建“库存表”而是通过事件明细汇总实时计算对库存资源的流入数量求和减流出数量求和得到当前库存。刚开始可能不习惯这种查询方式但跑过一段时间会发现它天然支持审计每一个数字都有事件依据而不是一个被覆盖的存量字段。4.6 典型业务在模型中的串联演示现在我用这三类事件串一下一个完整订单流程。客户 A 在某个时间点提交订单系统执行“销售订单事件”资源变动应收账款 1000库存商品 -1000。代理外部代理 A内部代理销售员 B。记录事件编号 EVT-001。仓库随后发货执行“发货事件”资源变动库存商品 -1000如果之前下单时还没扣这里扣同时产生成本数据。代理外部代理 A内部代理仓管员 C。记录事件编号 EVT-002并建立 EVT-001 → EVT-002 的关联。客户收货后发现质量问题申请退款。财务审核通过并打款执行“退款支付事件”资源变动现金 -1000应收账款 -1000冲抵。代理外部代理 A内部代理财务 D。记录事件编号 EVT-003并建立 EVT-001 → EVT-003 的关联。看到没整个过程没有任何一个“状态机”字段存一个时刻的订单状态而是由事件链条自然推导出当前状态。订单“已退款”等于存在一个销售事件和对应的退款事件“部分发货”等于销售事件下游关联了一个发货事件但还没有第二个发货事件的闭环。这种方式在复杂业务里特别耐折腾。5. 常见问题与排查技巧实录5.1 “事件”和“状态”傻傻分不清我见过很多新手会把“订单已支付”这种状态当成事件然后在事件表里存一堆“更新状态”的记录。这是最典型的误区。事件必须导致资源变动。如果只是状态流转、没有资源增减那它不是事件属于业务操作痕迹。排查方法很简单把这个动作带来的“资源量”写出来如果能写出“某资源增加/减少多少”它才可能是事件否则它只是一个状态快照可以单独放在业务表中甚至可以按事件聚合推导出来。比如“订单已支付”没有直接影响资源真正影响资源的是“收款事件”它增加了现金。5.2 资源数量应该用“事件明细”算还是“库存表”存我在做某个内部项目时最初为了查询性能设计了一张“商品库存表”每天凌晨跑批更新。结果每到月底对账就出问题某个时间点到底是多少库存为什么半夜变了后来改成按 REA 模型实时聚合计算虽然首次查询稍慢但数据完全可解释。如果你怕性能差可以引入“物化视图”按小时或按天刷新但底层的数据模型一定不能放弃事件明细。真正的库存是一个计算值不是存储值。如果你非要存那也要保证每次都基于事件流水计算后再覆盖否则就会出现“影子库存”。我个人的底线是用户界面上可以展示快照底层流转数据绝不直接覆盖。5.3 一对事件真的有且仅有两条资源记录吗不一定。一个销售事件可能涉及多种商品那就会有 N 条资源流出明细同时对应一条或多条应收账款流入明细。所以“事件-资源明细”表设计成了子表可以灵活挂多条。同理一个付款事件可能同时付清多个订单的应收所以你会看到一个现金流出事件关联多个应收账款减少明细。建模时不要强制一对一要能接受多对多。5.4 要不要把 REA 应用到所有功能模块没必要。REA 适合那些有经济资源、资金、实物流动的模块比如订单、库存、采购、财务、合同台账。至于纯文档管理、内容发布这类模块硬套 REA 只会加重复杂度。我在实践中的做法是先判断这个模块是否有明确的资源增减。如果没有直接走普通 CRUD别为了模型而模型。5.5 工具怎么选给一个保守但实用的建议先用表格软件把事件、资源、代理关系列出来然后用 draw.io 这类免费工具画图。不需要一开始就用重型建模工具因为模型的核心是逻辑是否闭环图只是沟通载体。等模型确定后再在设计文档里固化。如果你习惯用代码表示也可以写成 YAML 映射文件再生成 SQL Schema。6. 几个容易忽略的经验细节先说一个我踩过的坑事件时间到底存业务时间还是系统时间。在 REA 模型里业务事件一定要有“业务发生时间”和“记录创建时间”两个字段。比如一笔退款业务发生在某天但系统录入是第二天对账时必须按业务时间归期否则月底统计永远差半天。这个字段设计在开始就要定好不然数据量大了以后根本没法修正。再说代理的区分。不要把“操作人”和“业务代理”混为一谈。操作人是谁登录系统录了这笔记录业务代理是这笔业务实际服务的对象。在很多 ERP 项目中经常出现操作人代理和外部代理字段混用导致无法回答“这个客户到底和谁发生了交易”这个问题。这两个角色可以同时出现在事件表里但必须分开命名。还有一点资源单位要统一。做库存模型时有时候按“箱”入库按“个”出库如果不做单位换算事件明细里的数量加加减减就是一团乱麻。REA 模型里面每个资源最好绑定一个标准计量单位事件明细可以带单位但要有一个“标准数量”字段统一换算。这不是 REA 本身的问题但建模时如果不提前考虑后续统计会很痛苦。最后如果你把 REA 和领域驱动设计一起用可以把一个事件对应到一个领域事件代理对应到聚合根或实体资源对应到值对象。但这也不是必须的REA 本身已经足够独立适合任何语言和技术栈。后面如果再有人问我“业务建模怎么入手”我会直接拿这五个字回答找资源定事件分代理连关系校验闭环。你真做完一遍哪怕最后没按这张表建库脑子里对业务的理解也会比之前清楚很多。这大概就是 REA 给我最大的价值。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →