務(wù)建模:資源-事件-參與者模型實(shí)戰(zhàn)解析)
看到rea這個(gè)標(biāo)題我第一反應(yīng)不是“讀”也不是某個(gè)地區(qū)縮寫而是很多年前那個(gè)庫存項(xiàng)目——REAResource-Event-Agent。先跟你交個(gè)底這篇文章我按“資源-事件-參與者”建模來寫。如果你手里正捏著訂單、庫存、應(yīng)收應(yīng)付這類系統(tǒng)或者被借貸對(duì)賬、數(shù)據(jù)改來改去搞到頭大這篇文章能給你另一套建模思路把業(yè)務(wù)當(dāng)成一條事件鏈把余額當(dāng)成事件鏈的投影。前半部分講清楚REA到底在解決什么問題后半部分直接給出表結(jié)構(gòu)、SQL、踩坑經(jīng)驗(yàn)?zāi)憧梢哉罩?. 為什么無頭無尾的“rea”會(huì)被我解釋成業(yè)務(wù)建模1.1 一次庫存對(duì)賬事故讓我重新審視“記賬”第一次接觸REA是因?yàn)橐淮螏齑婺K重構(gòu)。上下游系統(tǒng)各自維護(hù)了一份“庫存量”每天都跑對(duì)賬任務(wù)??雌饋碓砗芎唵螏齑娴扔谌霂鞙p出庫但實(shí)際對(duì)賬時(shí)經(jīng)常差幾千件。查到最后發(fā)現(xiàn)倉庫有個(gè)流程是“先發(fā)貨后補(bǔ)單”補(bǔ)單時(shí)單據(jù)時(shí)間填的是前幾天財(cái)務(wù)系統(tǒng)按入賬時(shí)間記賬訂單系統(tǒng)按業(yè)務(wù)發(fā)生時(shí)間記賬。兩個(gè)時(shí)間不一致同一筆業(yè)務(wù)落到不同天里余額自然對(duì)不上。傳統(tǒng)做法是對(duì)賬日糾錯(cuò)哪邊不平手動(dòng)調(diào)一分錄。這種辦法治標(biāo)不治本因?yàn)橄到y(tǒng)里根本沒有一個(gè)“這盒物料在幾點(diǎn)幾分被哪個(gè)倉庫發(fā)出去了”的獨(dú)立事實(shí)。當(dāng)時(shí)我們最終得出的結(jié)論是與其在余額上反復(fù)找平衡不如把每一次資源變動(dòng)都記錄成不可變的事件誰要余額誰自己去重放。這個(gè)思路就是REA的雛形。1.2 REA是什么三個(gè)單詞一條價(jià)值鏈REA等于Resource資源、Event事件、Agent參與者。這套模型把企業(yè)經(jīng)濟(jì)活動(dòng)濃縮成三個(gè)問題什么東西在變發(fā)生了什么動(dòng)作誰參與了這個(gè)動(dòng)作舉一個(gè)很普通的例子一杯咖啡從豆子到顧客手里??Х榷故琴Y源咖啡豆入庫、烘焙、制作、售出、收款都是事件采購員、咖啡師、顧客都是參與者。庫存余額不是又一個(gè)實(shí)體而是“入庫事件總量減去出庫事件總量”的結(jié)果。這個(gè)直覺比一堆“借庫存貸應(yīng)付”的科目表直接不少。很多年前我第一次看到這三個(gè)詞心里也犯嘀咕這不是流水賬嗎等我真正把傳統(tǒng)借貸賬目拆開才發(fā)現(xiàn)事情沒那么簡單。1.3 它解決的核心問題把事實(shí)與結(jié)果分離REA最關(guān)鍵的一點(diǎn)是區(qū)分“事實(shí)”與“派生結(jié)果”。在傳統(tǒng)系統(tǒng)里庫存余額、應(yīng)收余額被當(dāng)成事實(shí)在REA里它們只是視圖。真實(shí)的事實(shí)是事件流。誰把余額表刪了都不怕事件還在就能重建誰把事件刪了那才叫事故。這個(gè)思想不是我的發(fā)明很早就有學(xué)者在企業(yè)建模領(lǐng)域討論過。后來我在技術(shù)評(píng)審里反復(fù)復(fù)用它的核心價(jià)值一是可解釋性任何余額都能從一個(gè)事件鏈推導(dǎo)出來二是可追溯性從發(fā)貨單能一路追溯到供應(yīng)商、采購單、付款三是可擴(kuò)展性新增業(yè)務(wù)類型時(shí)不用改一堆寄存的邏輯只是新增一種事件類型。但很多人聽到這里會(huì)追問那借貸記賬法到底哪里不行下面我就認(rèn)真聊聊這個(gè)。2. 傳統(tǒng)借貸賬目體系的卡脖子點(diǎn)記錄的是結(jié)論不是事實(shí)2.1 借貸分錄的隱含假設(shè)借貸記賬法本身是個(gè)非常聰明的設(shè)計(jì)它把每筆經(jīng)濟(jì)業(yè)務(wù)拆成“資產(chǎn)來源”和“資產(chǎn)去向”兩個(gè)方向用“有借必有貸借貸必相等”保證賬目平衡。問題不在于這個(gè)設(shè)計(jì)本身而在于系統(tǒng)運(yùn)轉(zhuǎn)久了之后大家把“科目余額”當(dāng)成了唯一事實(shí)源。在企業(yè)系統(tǒng)里總賬科目余額本質(zhì)上是聚合結(jié)果。聚合結(jié)果可以被重建但很多系統(tǒng)設(shè)計(jì)把它當(dāng)成不可替代的事實(shí)于是每次修數(shù)據(jù)都要做沖銷、補(bǔ)記、調(diào)賬。調(diào)來調(diào)去余額看齊了但業(yè)務(wù)事實(shí)被攪成一鍋粥。REA模型并不否定記賬功能它只是把借貸背后的經(jīng)濟(jì)事件、資源、參與者重新顯式建模。打個(gè)比方借貸體系相當(dāng)于直接放映電影截圖REA則把拍攝素材、剪輯腳本、演員分工都存了下來。截圖可以隨時(shí)從素材里再生成前提是素材不能被隨意覆蓋。2.2 多幣種、多倉、多租戶下的復(fù)雜度爆炸當(dāng)業(yè)務(wù)只有一臺(tái)收銀機(jī)時(shí)借貸表也夠用。但業(yè)務(wù)一復(fù)雜整個(gè)鏈條就開始卡脖子。拿多倉庫舉例A倉發(fā)貨到B倉數(shù)量只是從一個(gè)倉庫挪到另一個(gè)借貸里卻要先記“A倉庫存減少”再記“B倉庫存增加”。商品價(jià)值不一樣時(shí)還要補(bǔ)一筆“庫存差異調(diào)整”。業(yè)務(wù)邏輯每多一層這種調(diào)整分錄就多一類。多幣種更痛苦。一個(gè)訂單用美元成交、用人民幣對(duì)賬、中間還要評(píng)估匯兌損益借貸系統(tǒng)里的科目組合成倍增加。其實(shí)每種幣種對(duì)應(yīng)的現(xiàn)金是一個(gè)資源匯兌損益不過是一個(gè)特殊事件導(dǎo)致的資源價(jià)值重估。如果用REA事件表加一個(gè)幣種維度再定義一次重估事件即可。多租戶場景同樣如此。傳統(tǒng)建模往往把客戶編號(hào)掛在一個(gè)大維表里再用過濾條件把所有查詢包一層。REA天然以Agent區(qū)分主體授權(quán)關(guān)系本身就是模型的一部分不需要額外的租戶魔改。2.3 用事件對(duì)偶替代借貸符號(hào)再看深一層REA里有個(gè)概念叫對(duì)偶關(guān)系。我理解它就是在回答“為什么會(huì)有借貸相等”在現(xiàn)實(shí)經(jīng)濟(jì)行為中一個(gè)資源的流出往往伴隨另一個(gè)資源的流入。賣出咖啡是咖啡豆庫存流出對(duì)應(yīng)現(xiàn)金流入采購生豆是供應(yīng)商那邊的資源流出對(duì)應(yīng)我們這邊現(xiàn)金流出。兩個(gè)事件必須配對(duì)這個(gè)配對(duì)關(guān)系被顯式建模成事件與事件之間的鏈接。一旦把對(duì)偶關(guān)系建模出來系統(tǒng)就比傳統(tǒng)借貸表多了一個(gè)能力可以在不依賴會(huì)計(jì)科目的情況下推導(dǎo)出任意兩個(gè)資源之間的因果聯(lián)系??促~本時(shí)看到的不是孤立的金額而是資金怎么從A跑到B再到C的路徑。這也是我之前覺得“這不就是流水賬”不準(zhǔn)確的原因它確實(shí)是流水賬但是一張把流水中每一滴水的來源、去向、責(zé)任人都標(biāo)注清楚的流水賬。下一節(jié)我們把它的邊界真正落到表結(jié)構(gòu)上。3. 資源、事件、參與者三個(gè)構(gòu)件的邊界怎么拿捏3.1 Resource不是產(chǎn)品目錄而是價(jià)值載體Resource在REA里定義得很寬庫存商品、現(xiàn)金、應(yīng)收賬款、固定資產(chǎn)、知識(shí)產(chǎn)權(quán)甚至外包團(tuán)隊(duì)的時(shí)間盒都可以是資源。判斷標(biāo)準(zhǔn)很簡單第一它有價(jià)值第二它能被企業(yè)計(jì)劃和控制第三它能因?yàn)橐粋€(gè)事件增加或減少。對(duì)實(shí)施者來說最常見的誤區(qū)是把資源表當(dāng)成產(chǎn)品目錄。產(chǎn)品目錄里那些沒有庫存核算價(jià)值的元數(shù)據(jù)屬性應(yīng)該放在單獨(dú)的產(chǎn)品維表里資源表只管“算得清增減”的賬。比如同款咖啡豆有“在庫”和“在途”兩種狀態(tài)與其建模成兩個(gè)資源類型不如建模成同一資源類型下兩個(gè)獨(dú)立資源實(shí)例或者讓事件的時(shí)間戳自然表達(dá)在途與否。狀態(tài)永遠(yuǎn)是視圖不要讓它輕易滲透到資源主數(shù)據(jù)里。我在實(shí)際項(xiàng)目里養(yǎng)成了一個(gè)習(xí)慣每新建一個(gè)Resource之前先問自己一句未來會(huì)不會(huì)為它單獨(dú)創(chuàng)建Event如果不會(huì)它可能就是另一個(gè)資源上的屬性而不是獨(dú)立資源。這句話幫我砍掉過不少設(shè)計(jì)沖動(dòng)。3.2 Event不可再分的經(jīng)濟(jì)動(dòng)作不是操作日志Event是最容易被高估也最容易做錯(cuò)的一層。它要求記錄的是“不可再分的經(jīng)濟(jì)動(dòng)作”比如一次收貨、一次發(fā)貨、一次收款、一次轉(zhuǎn)賬。判斷標(biāo)準(zhǔn)可以拆成幾條是否能讓某個(gè)Resource的數(shù)量或價(jià)值發(fā)生變化是不是在真實(shí)世界里已經(jīng)客觀發(fā)生拆成更細(xì)的子事件后是否還有業(yè)務(wù)意義舉個(gè)例子“點(diǎn)擊下單”這個(gè)動(dòng)作本身不是經(jīng)濟(jì)事件它只是表達(dá)購買意圖“創(chuàng)建訂單”也不是經(jīng)濟(jì)事件訂單屬于契約真正的事件是“倉庫發(fā)出5箱貨”。很多團(tuán)隊(duì)設(shè)計(jì)表時(shí)把訂單創(chuàng)建、訂單審核、訂單配送、訂單完成全部塞進(jìn)一張大日志表美其名曰事件驅(qū)動(dòng)。這會(huì)讓后續(xù)做余量計(jì)算時(shí)非常痛苦因?yàn)橐话胗涗浉静辉撚绊懭魏钨Y源。事件表里最需要注意的是方向與事件類型的分開存儲(chǔ)。方向INFLOW/OUTFLOW是計(jì)算余額必須的信號(hào)類型是業(yè)務(wù)語義。同一種類型在不同場景下可以有不同的方向比如“銷售發(fā)貨”對(duì)庫存是OUTFLOW對(duì)銷售費(fèi)用可能是INFLOW。把兩者塞進(jìn)同一個(gè)字段后期會(huì)帶來數(shù)不清的隱患。3.3 Agent責(zé)任歸屬與授權(quán)邊界Agent解決的是三個(gè)問題誰發(fā)起了事件誰承擔(dān)了結(jié)果誰授權(quán)了這件事落地時(shí)最常用的做法是把參與者區(qū)分為內(nèi)部參與者與外部參與者。內(nèi)部參與者就是本系統(tǒng)內(nèi)的員工、角色外部參與者就是客戶、供應(yīng)商、承運(yùn)方。權(quán)限控制層面Authorization表專門在參與者和事件類型之間建立授權(quán)關(guān)系。比如倉庫主管可以授權(quán)發(fā)貨事件采購員可以發(fā)起采購合同。這個(gè)設(shè)計(jì)比單純?nèi)M(jìn)現(xiàn)成權(quán)限框架里更貼合業(yè)務(wù)事實(shí)因?yàn)闃I(yè)務(wù)授權(quán)不只是“能不能點(diǎn)按鈕”而是“能不能對(duì)該資源執(zhí)行該經(jīng)濟(jì)事件”。經(jīng)驗(yàn)之談Agent表不要企圖記錄所有組織架構(gòu)。部門層級(jí)、匯報(bào)線是組織信息不是經(jīng)濟(jì)參與者。把部門硬塞進(jìn)Agent里短期看只是多存幾個(gè)字段長期看部門調(diào)整時(shí)你根本不知道要不要新建一條資源變更事件。4. 一個(gè)銷售庫存業(yè)務(wù)的Schema從事件反推賬本4.1 先建Event表而不是先建余額表REA落地時(shí)我推薦從Event表起步。這跟傳統(tǒng)系統(tǒng)正好相反傳統(tǒng)系統(tǒng)先定庫存余額表然后為了追溯才去加流水表結(jié)果流水表永遠(yuǎn)滯后永遠(yuǎn)對(duì)不上。如果先定義事件所有報(bào)表都是從它派生出來的投影表加字段本質(zhì)上是加一種新事件類型改動(dòng)可控。下面這組DDL非常精簡適合銷售庫存場景起步。實(shí)際生產(chǎn)環(huán)境還需要補(bǔ)充租戶ID、數(shù)據(jù)版本號(hào)等字段這里先保留最核心的部分。CREATE TABLE resource ( resource_id BINARY(16) PRIMARY KEY, resource_name VARCHAR(64) NOT NULL, resource_type ENUM(ITEM, CASH, ACCOUNT, SERVICE) NOT NULL, unit VARCHAR(16) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE agent ( agent_id BINARY(16) PRIMARY KEY, agent_name VARCHAR(64) NOT NULL, agent_type ENUM(INTERNAL, EXTERNAL) NOT NULL, is_customer BOOLEAN NOT NULL DEFAULT FALSE, is_supplier BOOLEAN NOT NULL DEFAULT FALSE ); CREATE TABLE event ( event_id BINARY(16) PRIMARY KEY, event_no VARCHAR(32) NOT NULL UNIQUE, event_type VARCHAR(32) NOT NULL, direction ENUM(INFLOW, OUTFLOW) NOT NULL, resource_id BINARY(16) NOT NULL REFERENCES resource(resource_id), quantity DECIMAL(18,4) NOT NULL, unit_price DECIMAL(18,4), occurred_at DATETIME NOT NULL, recorded_at DATETIME NOT NULL, internal_agent_id BINARY(16) NOT NULL REFERENCES agent(agent_id), external_agent_id BINARY(16) REFERENCES agent(agent_id), UNIQUE KEY uk_dup_guard (event_no, resource_id) );注意event_no這個(gè)唯一鍵生產(chǎn)環(huán)境里對(duì)應(yīng)的是上游單據(jù)唯一號(hào)。兩筆相同的業(yè)務(wù)如果被重復(fù)錄入數(shù)據(jù)庫層會(huì)直接拒絕這是冪等的第一道防線。第5章我會(huì)細(xì)聊這條設(shè)計(jì)為什么重要。4.2 當(dāng)前庫存怎么算一條SQL直接求和需要當(dāng)前庫存時(shí)不需要去讀任何余額表直接把Event表按Resource和方向求和即可。SELECT r.resource_name, SUM(CASE WHEN e.direction INFLOW THEN e.quantity ELSE -e.quantity END) AS current_stock FROM event e JOIN resource r ON e.resource_id r.resource_id WHERE e.event_type IN (PURCHASE_ARRIVAL, SALES_SHIPMENT, STOCK_ADJUSTMENT) GROUP BY r.resource_id, r.resource_name;這里我把查詢限定在影響庫存的事件類型里而不是把Event表里所有記錄都拉進(jìn)來。很多新手會(huì)把所有事件全加一遍結(jié)果把收款、賬務(wù)調(diào)整之類的INFLOW也加進(jìn)了庫存。所以在設(shè)計(jì)事件類型時(shí)應(yīng)該給每類事件定義“它影響哪類資源、方向是什么”形成一張事件類型注冊(cè)表后續(xù)所有查詢都依賴這張表。4.3 應(yīng)收應(yīng)付自動(dòng)派生發(fā)票和收款本身就是一進(jìn)一出REA還有一個(gè)非常好用的紅利應(yīng)收賬款余額不是單獨(dú)維護(hù)的而是從發(fā)票事件和收款事件里推導(dǎo)出來。每張發(fā)票是一個(gè)INFLOW事件經(jīng)濟(jì)資源是“應(yīng)收賬款”每次收款是一個(gè)OUTFLOW事件把應(yīng)收賬款減掉。合在一起就是當(dāng)前客戶欠款SELECT a.agent_name AS customer, SUM(CASE WHEN e.event_type INVOICE THEN e.quantity * e.unit_price ELSE 0 END) - SUM(CASE WHEN e.event_type PAYMENT THEN e.quantity * e.unit_price ELSE 0 END) AS outstanding FROM event e JOIN agent a ON e.external_agent_id a.agent_id JOIN resource res ON e.resource_id res.resource_id WHERE res.resource_type ACCOUNT AND e.event_type IN (INVOICE, PAYMENT) GROUP BY a.agent_id, a.agent_name;這種設(shè)計(jì)下退款就是一筆新的負(fù)數(shù)量事件而不是去修改原事件。歷史庫存增量沒有被破壞審計(jì)鏈路也清晰得多。5. 報(bào)表、審計(jì)與事件溯源從REA得到的三個(gè)紅利5.1 報(bào)表不再是“查余額”而是“重放行為”傳統(tǒng)報(bào)表是查一張快照表REA是重放一段行為流。我們把余額理解成暫停幀我要看昨天的庫存不依賴定時(shí)快照只需要加一個(gè)時(shí)間過濾條件重放到昨天那一刻。這帶來一個(gè)額外能力任何業(yè)務(wù)問題都能回到原始行為去排查。比如財(cái)務(wù)問“這批貨物價(jià)值怎么波動(dòng)這么大”傳統(tǒng)系統(tǒng)只能看到調(diào)賬后的金額REA里可以把入庫、出庫、報(bào)損、移倉事件全部攤開每一步寫的是什么原因一目了然。它天然適合做審計(jì)追蹤因?yàn)槭录鞅旧砭褪且还P完整的行為賬。5.2 不可變事件、冪等鍵、業(yè)務(wù)時(shí)間戳三個(gè)紅線必須一起立想享受REA的好處前提是把底層規(guī)則立住。我在項(xiàng)目中始終堅(jiān)持三條紅線。第一事件表只做INSERT禁止UPDATE和DELETE。哪怕錄錯(cuò)了也只能再補(bǔ)一條調(diào)整事件把錯(cuò)誤影響抵消掉。聽起來繁瑣但換來的是一勞永逸的簡單任何時(shí)刻的事件流都是不可篡改的歷史。第二冪等鍵必須在數(shù)據(jù)庫層兜底。最穩(wěn)妥的方式是給業(yè)務(wù)事件號(hào)加唯一索引再配合外部消息或批處理任務(wù)里的去重邏輯。沒有唯一索引再好的應(yīng)用層去重也可能在并發(fā)下漏掉。第三業(yè)務(wù)時(shí)間戳與系統(tǒng)入賬時(shí)間戳分開存。occurred_at記錄真實(shí)業(yè)務(wù)發(fā)生時(shí)間recorded_at記錄系統(tǒng)落庫時(shí)間。報(bào)表按業(yè)務(wù)時(shí)間聚合問題排查按入賬時(shí)間查詢兩者互不干擾??鐣r(shí)區(qū)、跨系統(tǒng)的場景下這個(gè)雙時(shí)間戳設(shè)計(jì)能避免很多扯皮。5.3 和事件溯源、微服務(wù)架構(gòu)的配合REA模型與事件溯源架構(gòu)天然親和。微服務(wù)拆分時(shí)服務(wù)之間傳遞的往往就是業(yè)務(wù)事件比如庫存扣減、訂單付款成功、退款發(fā)起。REA正好給這些事件定義了標(biāo)準(zhǔn)范式有資源、有參與者、有方向、有時(shí)間。但這里我有一個(gè)明確的提醒不要把REA事件和消息隊(duì)列里的消息畫等號(hào)。REA事件是已經(jīng)發(fā)生的經(jīng)濟(jì)事實(shí)持久化在主庫里MQ消息是傳輸載體可能丟失、亂序、重復(fù)。正確做法是把REA事件作為唯一事實(shí)源消息只是它的一個(gè)副本或者投影。下游服務(wù)收到消息后可以異步更新自己的投影表投影過期了就從REA事件重放一次。6. 用REA建模時(shí)最容易翻車的三個(gè)坑6.1 把Event表寫成操作日志第一次落地REA時(shí)我見過最典型的翻車現(xiàn)場就是Event表被當(dāng)成萬能的“操作日志”。用戶登錄、按鈕點(diǎn)擊、頁面瀏覽也往里插。結(jié)果查詢余額時(shí)全表掃描越來越慢而且業(yè)務(wù)查詢根本不知道該過濾哪些事件類型。判斷標(biāo)準(zhǔn)我再重復(fù)一遍只有會(huì)引起Resource增加或減少的動(dòng)作才配稱為經(jīng)濟(jì)事件。行為日志請(qǐng)單獨(dú)放到日志系統(tǒng)兩套數(shù)據(jù)的生命周期、存儲(chǔ)策略、訪問模式完全不同混在一起只會(huì)兩頭難受。6.2 過度建模把流程節(jié)點(diǎn)全部事件化另一種相反的極端是把所有東西都建模成Event。訂單創(chuàng)建、訂單審核、訂單配貨、訂單發(fā)貨、訂單完成全部分解成一串事件。表面上挺RER實(shí)際上業(yè)務(wù)查詢需要跨十幾個(gè)事件類型才能拼出一個(gè)訂單狀態(tài)開發(fā)效率急劇下降。原因在于訂單本身是契約不是經(jīng)濟(jì)事件。契約表達(dá)的是雙方約定事件表達(dá)的是資源實(shí)際增減。下單時(shí)不產(chǎn)生資源增減貨物出庫才產(chǎn)生。契約表和事件表分開建模一個(gè)管承諾一個(gè)管事實(shí)這樣訂單狀態(tài)自然有了歸屬。6.3 忽視投影層的性能設(shè)計(jì)不能只建一個(gè)Event表就上線。事件表會(huì)無限增長業(yè)務(wù)報(bào)表不可能每次都全表重放。合理的做法是在同一事務(wù)里寫事件和更新投影表或者用異步消費(fèi)者更新投影表。庫存余額、應(yīng)收余額、銷量統(tǒng)計(jì)這些高頻查詢走投影表歷史分析走事件表。我曾經(jīng)為了省事報(bào)表直接對(duì)Event表做全量聚合數(shù)據(jù)量到千萬級(jí)以后查詢直接卡死。后來加了一張庫存投影表每次寫入事件時(shí)同時(shí)更新投影報(bào)表查詢從秒級(jí)變成毫秒級(jí)。所以建議把Event表當(dāng)作唯一事實(shí)源但不要讓它成為唯一的查詢?cè)础?.4 把REA套用到根本不需要的地方REA也有明確的適用邊界。純文檔管理、簡單CMS、個(gè)人賬本這類業(yè)務(wù)資源流動(dòng)極少事件類型單一引入一套完整的事件模型只會(huì)增加表和邏輯的復(fù)雜度。倒不如用普通業(yè)務(wù)表更直接。什么時(shí)候應(yīng)該用REA我的判斷是業(yè)務(wù)里有多種資源頻繁流動(dòng)存在多個(gè)參與者和多步審批授權(quán)同時(shí)你還特別需要追溯歷史行為和生成彈性報(bào)表。只有這類系統(tǒng)REA的投資才值得。7. 我的落地最小集十個(gè)表起步7.1 表清單一個(gè)保守但完整的REA骨架如果你準(zhǔn)備在一個(gè)真實(shí)業(yè)務(wù)里嘗試REA我建議從下面這組表開始。不追求一次覆蓋全部理論先保證核心鏈路跑得通。序號(hào)表名作用備注1resource資源主數(shù)據(jù)庫存、現(xiàn)金、應(yīng)收賬戶都在這里2agent參與者主數(shù)據(jù)內(nèi)部角色與外部客戶/供應(yīng)商3authorization參與者對(duì)事件類型的授權(quán)控制誰可以發(fā)起哪些行為4event經(jīng)濟(jì)事件明細(xì)唯一的原始事實(shí)來源5event_duality事件對(duì)偶關(guān)系發(fā)貨事件對(duì)應(yīng)收款事件6contract契約頭訂單、采購單的承諾信息7contract_item契約行訂單行、商品、數(shù)量、價(jià)格8policy業(yè)務(wù)規(guī)則折扣、信用額度、發(fā)貨約束9projection_inventory庫存投影表高性能當(dāng)前庫存10projection_receivable應(yīng)收投影表高性能客戶欠款這十張表并不復(fù)雜但已經(jīng)覆蓋了一個(gè)普通銷售庫存業(yè)務(wù)從承諾到履行再到結(jié)算的完整閉環(huán)。7.2 老系統(tǒng)遷移的三步走老系統(tǒng)里沒有事件只有一堆余額和賬單怎么遷移我實(shí)踐下來比較穩(wěn)的順序是三步。第一步把歷史余額做成初始事件。先把當(dāng)天庫存、應(yīng)收、應(yīng)付的存量確認(rèn)下來作為時(shí)間起點(diǎn)生成一條“期初事件”。這是整條事件鏈的地基。第二步按單據(jù)往回補(bǔ)近期事件。從最近三個(gè)月起把采購入庫單、銷售發(fā)貨單、收款單、付款單都反寫成Event表。過程中會(huì)遇到單據(jù)編號(hào)缺失、上游系統(tǒng)時(shí)間不準(zhǔn)等問題盡量在轉(zhuǎn)化期就把臟數(shù)據(jù)清掉。第三步雙寫并行并做日級(jí)對(duì)賬。老系統(tǒng)繼續(xù)跑一段時(shí)間新系統(tǒng)同時(shí)接收新業(yè)務(wù)事件每天比對(duì)庫存與應(yīng)收余額。只有連續(xù)兩到三周誤差為零才敢切流量。7.3 什么情況下我勸你“別用REA”最后說句掏心窩的話千萬別因?yàn)檫@篇文章覺得REA是銀彈。如果你的團(tuán)隊(duì)只有兩三個(gè)人業(yè)務(wù)一個(gè)月改不了一次余額表直接一把梭反而更快。我個(gè)人的體會(huì)是REA最大的價(jià)值出現(xiàn)在業(yè)務(wù)進(jìn)入高速變化期的時(shí)候。那時(shí)候你改一個(gè)庫存規(guī)則傳統(tǒng)系統(tǒng)要?jiǎng)悠甙藗€(gè)模塊REA系統(tǒng)可能只加一種事件類型。但這是用前期的建模成本換來的——你需要愿意接受“事件先于視圖”的設(shè)計(jì)思想并且有足夠的紀(jì)律性維護(hù)事件不可變、冪等和時(shí)間戳這三條底線。最后再分享一個(gè)小技巧技術(shù)評(píng)審上遇到“REA是什么”這種問題別只背Resource、Event、Agent三個(gè)單詞。把一杯咖啡的完整生命周期講一遍——從采購豆子、烘焙、制作、售出到收款每個(gè)環(huán)節(jié)誰是資源、誰是事件、誰是參與者鏈路天然就是一套R(shí)EA。把故事講清楚比背概念有用得多。