资讯详情

REA模型实战:用资源-事件-参与者重构业务建模与领域设计

📅 2026/10/21 13:26:22 | 华诺云谱 👁 阅读
REA模型实战:用资源-事件-参与者重构业务建模与领域设计
聊业务建模的时候我经常被问到一类具体得不能再具体的问题订单表怎么设计才不乱库存数量到底要不要冗余状态机画到第几层才算完这些问题看起来是表结构问题但追到根上你会发现缺的是一套能把业务“看穿”的视角。今天想聊的REA模型——Resource-Event-Agent资源-事件-参与者——就是我一直很推崇的那把尺子。它不是一套新框架也不是某个中间件而是一种从会计信息化领域走出来的业务语义建模方法。如果你正在做系统重构、领域建模、微服务拆分或者单纯想把一团乱麻的业务规则理清楚这篇文章应该能给你一个完全不同的思考工具。我第一次接触REA模型时脑子里的第一反应是这不就是把业务拆成“谁、对什么、做了什么”吗后来踩了一堆坑、拿它梳理完几个真实项目之后才明白越是看起来简单的模型背后越藏着一套完整的价值观——它逼着你放弃“以单据为中心”的惯性回到“以经济现实为中心”的视角。这篇内容我会从设计思想讲到实操落地从库表设计讲到领域模型最后再把我自己踩过的坑和排查经验一并交底。1. REA模型的设计思想与选型逻辑1.1 传统建模为什么不够用从一张订单表说起先看一个几乎每天都在发生的需求客户下单我们要发货、开票、收钱。大多数人的第一版设计是这样的——建一张订单表、一张订单明细表再来一张发票表、一张收款表然后在订单表上挂一个状态字段从“待审核”一路排到“已完成”。这套设计能用吗能用而且曾经大量系统都是这么跑的。但只要你在这个系统里活下去半年就会逐渐感受到那些别扭状态字段越加越多业务规则散落在if/else里财务问“这个订单到底发没发货、钱收齐没有”你得从三四张表里拼数据运营想统计“平均成交周期是多长”发现根本没有记录“成交”这个时刻。这背后的根因很有意思传统建模在骨子里是“以现状为中心”的它记录的是“当前订单是什么状态”却很少记录“业务是怎么变成这个状态的”。状态多了以后本质是事件被压扁成了字段。用会计的那套话说你建的是流水账但你需要的是资产负债表和利润表——你需要知道资源从哪里来、到哪里去而不是只盯着“现在还剩多少”。REA模型恰恰是反过来建模的。它要求你把业务表达成“资源在事件中被参与者互换或转换”所以一个销售订单根本就不是核心实体真正的核心是商品资源流出了、现金资源流入了这两个经济事件由客户、销售员、发货员这些参与者完成。至于订单、发票、合同都不过是事件留下的凭据。这样一个视角转换会让你重新审视系统里的每一个表很多纠结自然会消失。1.2 REA语义拆解资源、事件、参与者分别是什么REA模型的三要素字面上非常朴素但每一个都需要仔细掂量。Resource资源指的是“有价值、可以被持有、可以被转移的东西”。商品、现金、库存、应收账款、生产工时、服务里程这些是资源。反过来订单、合同、审批记录、报价单这些“信息载体”不是资源。一个简单的判断标准它能不能被一个事件增加、被另一个事件减少如果能它就是资源如果它只是一次业务行为的凭证那它其实是从属于事件的文档。Event事件指的是“改变资源存量或可用性的经济行为”。卖出商品是一个事件因为库存少了、同时产生了应收账款收到货款是一个事件因为现金多了、应收账款清了。事件必须是一个已经发生、不可撤销的事实而且一定会牵动资源的变化。这里有一个新手绕不过去的坎注册、审核、查询、审批通过这些算不算事件严格来说不算因为它们不直接改变经济资源。Agent参与者指的是“卷入事件的人或组织”。它分为内部参与者比如销售员、发货员、财务外部参与者比如客户、供应商。参与者建模的粒度直接决定你的权限和审计能力。这三个概念之间还天然带出几类关系资源流关系表示一个事件使某个资源流入或流出参与关系表示某个参与者参与了某个事件职守关系表示参与者有权限去操作某个资源互换关系表示两个经济事件配对比如销售方发货事件和购买方收货事件构成一次交换。这四类关系本质上就是业务语义的骨架画出来之后整个流程是闭环的什么环节缺人、什么环节缺资源一目了然。我经常用一个厨房的类比来解释REA。开餐厅这件事食材、菜品、现金是资源“采购食材”“炒菜”“上菜”“收银”是事件厨师、服务员、顾客是参与者。“点菜单”重要吗重要但它只是事件的凭据不是经济资源本身。你复盘餐厅一天赚不赚钱要盯的是食材被消耗了多少、菜品卖出去多少、现金收进来多少而不是菜单本身。本质上REA就是让信息系统像这样去理解业务。1.3 REA与现代设计方法的关系为什么今天值得重新学你可能会觉得这套东西是上世纪八十年代的产物放到今天容器化、云原生、人工智能的时代是不是太老了我的看法恰恰相反。REA模型跟领域驱动设计、事件溯源以及微服务拆分之间的亲和度高得惊人。先说领域驱动设计。DDD里面最让人头疼的就是聚合边界怎么画。一个订单聚合里到底塞多少人、多少实体很多人凭感觉切结果切出来的聚合不是太碎就是太肿。REA给了一个相对可靠的判断依据围绕一个经济事件把参与它的资源与参与者圈在一起就是一个非常自然的高内聚语义单元。比如“销售发货”事件加上商品资源和发货员、客户天然就是一个聚合“收款”事件加上现金资源和财务、客户是另一个聚合。两者通过互换关系相连但生命周期不同完全应该拆开。这样一拆跨聚合的最终一致性边界也明确了。再说事件溯源。事件溯源系统里事件是唯一的真相源。REA模型的事件本来就是不可变的事实正好与这种存储方式无缝对接。你把REA事件表设计成追加写入、不更新不删除长期下来审计、回放、追溯都能得到很好的支持。说到底REA不是什么高深理论它就是一套“事件驱动”的业务语义框架只不过它比你在工程层面想的“event”更贴近业务本质。最后聊微服务拆分。服务边界到底切在哪很多人按“订单服务”“用户服务”“库存服务”来切结果是服务间调用绕成一团。REA鼓励你按事件循环来切。一个事件循环一般由两个配对的经济事件组成比如“发货收款”“收货付款”。一个循环内的强一致性业务可以放进一个服务循环之间用异步消息或事件流来同步。这样的服务边界基本不会因为“业务架构调整”而频繁重画。2. 核心细节要素识别与建模规则2.1 资源识别看价值流而不是看数据流资源识别看起来简单却是建模时出错率最高的环节。我常在评审会上看到团队把“客户档案”“合同文件”“审批记录”这些都建模成资源结果REA图画出来资源满天飞事件反而没几个那模型就变味了。识别资源的实用标准有三条。第一它能否被某个参与者持有、使用或转移第二它是否有明确的价值度量方式比如数量、金额、工时第三它是否能被经济事件增加或减少并且这种变化在业务上需要被记录和追踪。三条都满足它才有资格作为Resource出现在图里。举个例子客户档案能不能被持有和转移能但它是“主数据”它描述的是参与者的信息而不是经济资源。合同文件能增加减少吗能但那只是文件归档的过程不构成经济资源的流动。应收账款则不一样它是客户欠你的钱虽然没有实物形态但它能被收款事件减少、能被销售事件增加是实打实的经济资源。所以真正应该进REA模型的资源清单通常是商品、库存、现金、应收账款、应付账款、固定资产、生产工时、服务工作量这几种。我还想补充一个容易被忽略的细节资源的粒度。库存资源到底是“SKU级”还是“批次级”应收款到底是“订单级”还是“客户级”这取决于你所在业务的管理颗粒度。REA模型不规定你必须用某个粒度但有一个原则资源粒度和经济事件的记录粒度必须一致。如果你每次发货都精确到批次那么库存资源就不该只建模到SKU。否则后期一做批次追溯就会发现事件对不上资源只能靠一堆扩展字段塞补丁。2.2 事件建模不是所有“发生的事”都是事件事件是REA模型的心脏也是最容易被滥用的概念。我见过的错误模型里最常见的问题就是把状态流转的节点当成了事件。比如“订单审核通过”“合同已归档”“任务已关闭”——这些确实表示系统里发生了某个动作但它们改变经济资源了吗往往没有。它们改变了文档状态而不是经济现实。那什么才是真正的经济事件一个直接判据事件导致某个资源产生了流入或流出。销售发货导致库存减少、应收账款增加是事件客户付款导致现金增加、应收账款减少是事件生产领料导致原材料减少是事件完工入库导致成品增加是事件。审核通过只是让“发货”这件事获得了许可它本身不改变资源存量所以在REA图里它要么作为事件的前置条件记录要么就干脆不进核心模型。这里还有一个重要的概念双经济事件。一次完整的交换必须有两个方向相反的经济事件配对。销售方视角是“发出商品”和“收到现金”采购方视角是“收到商品”和“付出现金”。这组配对关系叫互换Exchange它是REA模型保证业务闭环的关键。如果只建模“发出商品”而不建模“收到现金”那应收账款就永远挂账财务闭不了环。如果只建模“收到货物”而不建模“付出现金”那应付账款就永远悬空。生产场景下不是互换而是转换Conversion。原材料被消耗、人工工时被投入、产成品被产出这三个事件共同构成一个生产事件组。转换事件组内部不需要两个配对事件但有一个要求投入的经济价值与产出的经济价值在模型层面要能关联追踪。否则成本核算只能靠月底一次性倒算过程追溯完全缺失。2.3 参与者建模粒度决定权限边界与审计能力参与者看起来最直白——就是人嘛。但真到建模时“同一个销售员把销售单填了又把发货单也批了”这种问题马上会逼你思考参与者的粒度。REA模型里的职守关系专门处理这个某个参与者能否执行某个事件对资源的操作。它背后对应的是内部控制里的职责分离原则——一个人不能同时兼任两个互相制约的职责。我建议在落地时这样处理首先把参与者区分为内部与外部两类内部参与者关联到组织架构外部参与者关联到客户或供应商档案。这是两条独立的维度不要放进同一个字段。其次针对“一个人多个身份”的情况把“人”和“角色”分开建模。人是一个参与者实例角色是他在某个事件里承担的身份。一个自然人既可以是销售员又可以是审批人但在“销售事件”和“审批事件”里他对应的是两个不同的角色实例。这样审计时可以回答“谁在哪个环节干了什么”权限控制也可以做到事件级。参与者粒度的另一个经验是不要把部门建得太大。比如“销售部”作为一个参与者看起来省事但你一旦要追责到个人“销售部”就变成了一堵墙。反过来把每个员工都建为参与者看似规范可如果系统里根本没有针对个人的统计与权限需求那就是过度设计。粒度的锚点仍然是“职守关系”——你需要对谁的权限负责你就建模到谁的粒度。3. 实操用一个B2B订货场景把REA落地3.1 场景说明与当前痛点我拿一个典型的B2B订货系统来演示REA建模的全过程。某零部件公司业务模式是客户在线上平台下单销售审核后仓库发货财务对账后客户打款。现有系统的问题非常典型订单上挂了超过二十个状态从“待提交”到“已完成”再到“已关闭”财务对账靠手工导Excel任何一个人问你“某笔订单到底发没发货、钱收齐没有、是谁批的”都要在一堆表和状态流转里翻半天。建模目标不是推翻现有系统而是先用REA把核心业务重画一遍把“业务主干”理清楚再反向指导表和接口的设计。3.2 分步建模推演从资源清单到事件网络第一步列出资源。这个场景里最核心的资源是库存商品、现金银行存款、应收账款。如果再展开一点还可以有配件的批次库存、预收账款。但第一版建模我建议克制一点先把“商品、现金、应收”最主干的三条资源建好后面再迭代。第二步列出经济事件。逐个人工分析业务动作只保留改变资源的事件。客户下单不是经济事件它只是意向表达不改变任何经济资源审核通过也不是经济事件只是许可真正的事件列表是销售发货商品流出应收增加、客户付款现金流入应收减少、采购收货商品流入应付增加、对供应商付款现金流出应付减少。如果商品有退货场景再加销售退货商品流入应收减少等事件。第三步给每个事件挂上参与者。销售发货事件的内部参与者是发货员外部参与者是客户客户付款事件的外部参与者是客户内部参与者是财务。这一步做完审计链已经清晰了。第四步建立事件与资源的流关系以及事件之间的互换关系。一个销售发货事件对应一个客户付款事件两者通过互换关系配对。采购收货事件与付款事件同理。把这些关系整理成一张表看起来就像这样经济事件资源流入资源流出内部参与者外部参与者配对事件销售发货应收账款增加库存商品减少发货员客户客户付款客户付款银行存款增加应收账款减少财务客户销售发货采购收货库存商品增加应付账款增加采购员供应商供应商付款供应商付款应付账款减少银行存款减少财务供应商采购收货这张表一旦建出来之前那些“订单状态”到底是什么瞬间就清楚了。订单、状态、审批记录全都退回到“凭据”和“控制信息”的位置不再跟经济事实混在一起。后续系统设计时只需要维护好这张事件网络任何财务问题都能直接回答。3.3 落地一从REA到核心表结构现在把REA模型落到数据库设计上。核心是四张表资源表、事件表、参与者表以及关系表。为了控制篇幅我直接给出一版最简结构。资源表记录经济资源比如商品、应收账款这类。事件表记录已经发生的经济事件这是系统的核心事实表。参与者表记录内部与外部参与者。关系表则负责表达事件与资源、事件与参与者、事件与事件之间的配对关系。示例SQL如下create table economic_resource ( id bigint primary key, resource_type varchar(32) not null, -- product / cash / receivable / payable name varchar(128) not null, unit varchar(16) not null ); create table agent ( id bigint primary key, agent_type varchar(16) not null, -- internal / external name varchar(128) not null, org_id bigint ); create table economic_event ( id bigint primary key, event_type varchar(32) not null, -- sale_delivery / customer_payment / purchase_receipt … occurred_at timestamptz not null, document_no varchar(64) -- 关联原始单据号相当于凭据 ); -- 事件与资源的流关系directionin/out create table resource_flow ( id bigint primary key, event_id bigint not null references economic_event(id), resource_id bigint not null references economic_resource(id), direction varchar(8) not null, -- inflow / outflow quantity numeric(18,4) not null, unit_price numeric(18,4), amount numeric(18,4) not null ); -- 事件与参与者的参与关系带角色 create table event_participation ( id bigint primary key, event_id bigint not null references economic_event(id), agent_id bigint not null references agent(id), role varchar(32) not null -- seller / auditor / receiver / finance ); -- 互换关系give_event与receive_event配对 create table exchange_link ( id bigint primary key, give_event_id bigint not null references economic_event(id), receive_event_id bigint not null references economic_event(id) );这套结构看起来简单但它解决了几个原先很难缠的问题。库存怎么算不靠任何冗余的“当前库存”字段而是通过对resource_flow表中product资源的流入流出求和得出。应收账款怎么算同理对receivable资源求和。这样的设计天然支持随时回放任意时间点的资源存量审计时不需要去翻几十个状态字段来判断历史时刻的真相。3.4 落地二从REA到领域模型与代码数据库落地之后代码模型也可以跟着REA的思想走。在领域层我把事件实体看作核心聚合根。下面是一个精简到只剩骨架的示例重点看结构不要纠结具体实现interface EconomicEvent { eventId: string; eventType: string; occurredAt: Date; documentNo: string; flows: ResourceFlow[]; participations: Participation[]; } interface ResourceFlow { resourceId: string; direction: inflow | outflow; quantity: number; unitPrice: number; amount: number; } interface Participation { agentId: string; role: string; } interface ExchangeLink { giveEventId: string; receiveEventId: string; }这一层模型非常稳定因为它只跟随业务事实变化而不是跟随页面改版或接口调整。你在写“创建订单”的接口时代码要做的事情不是把订单塞进一张大而全的订单表而是实例化一个“销售发货”事件记录它的flows与participations然后在数据库里追加这一事件。查询侧的视图模型比如订单列表、应收账龄表则可以通过读模型从事件表聚合出来。这就是命令模型和查询模型分离的实践写入走事件读取走视图。REA提供了一个非常自然的分界线——事件表是命令模型的核心各种报表视图是查询模型。你在业务代码里再也不用维护“状态机流转”了因为状态本质上是事件序列的一种推导结果需要时去算而不是存一个随时可能过期的字段。4. 常见问题与避坑实录4.1 建模阶段高频踩坑速查表我把自己在实践里经常看到的问题整理成了一张速查表方便你对照自查问题典型表现原因分析解决思路把订单当资源模型里一堆“订单余额”“订单状态”混淆了信息载体与经济资源订单只是凭据真正资源是商品/现金/应收事件里冗余库存量库里同时存事件明细和当前库存字段想“省事”结果两端容易不一致当前库存由事件流推导禁止单独冗余遗漏内部参与者事件表只有外部客户没有操作人只关心业务数据不关心责任链每个事件必须挂内部参与者与角色资源用金额不用数量库存资源只记总价没有数量和单价简化数据导致审计无法复核同时记录quantity、unit_price、amount把“状态”当事件事件表里出现“审核通过”“已关闭”把系统动作等同为经济行为只有改变资源的事件才是经济事件事件表频繁更新发现错误直接改事件记录违背事件不可变性原则事件只能追加纠正用反向事件或冲正事件第一条和第五条基本上是所有团队都会踩的我当初也无一例外。记住一句话REA模型里没有“状态”只有“事实序列”。状态是事件推出来的视图不是可以被随意修改的属性。4.2 存量系统怎么用REA治理而不是推倒重来听到REA模型很多人第一反应是“那我旧系统不是要重写”其实完全不必。旧系统承载了大量历史数据和稳定功能贸然推翻风险极高。我推荐的方式是把REA当成一把“治理尺子”先梳理再迁移。具体做法是三步第一步做事件映射。把旧系统的主干业务操作逐条列出来和REA事件对照。你会发现旧系统“修改订单状态”的操作凡是涉及“发货”“收款”“退换货”的都能映射成经济事件凡是“审核”“归档”“开启”的都不算经济事件可以留在原文档流程里。第二步抽新核心。只把经济事件抽出来落到一套附加的事件表里新增业务开始按REA写入老数据慢慢补录或批次导入。第三步老查询逐步切换。把原先依赖状态字段的报表改成基于事件表的聚合查询一次改一个报表直到旧状态字段可以被安全下线。这样做的最大好处是你不需要一次性迁移几千张表业务连续性有保障团队也能在过渡期里逐步建立对REA模型的信心。我见过一个团队用了这种方式三个月只加了六张事件表就把历史遗留的订单对账问题彻底解决了完全没有大动作。4.3 什么时候别硬套REA前面都在讲REA多好用但这并不代表它适合所有场景。实事求是地讲如果你做的系统是纯信息管理类比如一个公告后台、一个简单的任务待办清单没有经济资源的转移没有明确的参与者职责分离那套REA模型只会徒增复杂度。两张家表就能解决的问题硬建模成十张事件表是在给团队制造负担。另外如果系统里根本没有审计要求、没有对账场景、没有跨团队协作的职责分离需求REA的大部分语义化优势也体现不出来。判断适不适合用REA核心就看一点你是否需要回答“某个资源在某个时间点上有多少、是谁经手、为什么变化”这类问题需要那REA就是值得投入的不需要就别折腾。即使决定使用也要控制范围。我建议第一版只覆盖两条主要价值链销售循环和采购循环。不要试图一气把财务、库存、生产、售后全部建模完成。REA的美感在于它足够克制一旦你把它膨胀成一个过度复杂的“万能模型”它就会变成一个谁也看不懂、谁也维护不了的摆设。最后再分享一点个人体会。我后来梳理老项目时才发现REA模型带给团队最大的价值其实不是那几张新表而是它统一了所有人讨论业务的语言。以前开会争论“订单到底算不算资产”“状态该不该加一个字段”现在大家会先问三个问题这里面的资源是什么事件是什么参与者是谁这三个问题一问大部分争论自然就消散了。模型会过时但“用事实和资源来理解业务”的思维方式确实能一直用下去。你拿一个最头疼的核心流程试试画一遍REA可能也会有这种感觉。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑