事件溯源和命令和查詢責(zé)任分離模式

Achievement provides the only real pleasure in life

CRUD模式

在 Web Service 中我們一般分為三層:

  1. Controller
  2. Service
  3. Repository

而作用于這三個(gè)層次的對(duì)象有:

  1. DTO (Data Transfer Object) 數(shù)據(jù)傳輸對(duì)象
  2. BO (Business Object or Domain Object)
  3. DAO (Create/Retrieve/Update/Delete)

對(duì)于這些對(duì)象有基本操作有 CRUD , CUD 是數(shù)據(jù)更新, R是數(shù)據(jù)讀取, 兩者大不相同, 前者有副作用, 一般需要事務(wù)管理, 而后者只是讀取數(shù)據(jù),無(wú)任何副作用。

單機(jī)應(yīng)用無(wú)所謂, 傳統(tǒng)數(shù)據(jù)庫(kù)系統(tǒng)也對(duì)強(qiáng)一致性做了很好的 ACID 支持,而到了分布式應(yīng)用, 使用 NOSQL 系統(tǒng), 傳統(tǒng)的 CRUD 的問(wèn)題就凸顯出來(lái)了。

多個(gè)應(yīng)用同時(shí)修改一條記錄, NOSQL 怎么保證數(shù)據(jù)一致性呢, NOSQL 沒有傳統(tǒng)數(shù)據(jù)庫(kù)那樣的行級(jí)鎖。從下兩個(gè)模式可以解決這個(gè)問(wèn)題

事件溯源模式

Event Source 模式由來(lái)已久, Greg Young 在 DDD(Domain Driven Design) 的應(yīng)用中做了更多的闡述.
它通過(guò)事件來(lái)表示一個(gè)領(lǐng)域?qū)ο?Aggregation 聚合)的完整狀態(tài), 通過(guò)自該對(duì)象創(chuàng)建以來(lái)的一系列事件, 按時(shí)事件產(chǎn)生時(shí)的順序進(jìn)行重放, 來(lái)重建對(duì)象的當(dāng)前狀態(tài).

它使用只追加存儲(chǔ)來(lái)記錄對(duì)數(shù)據(jù)采取的完整系列操作,而不是僅存儲(chǔ)域中數(shù)據(jù)的當(dāng)前狀態(tài)。 該存儲(chǔ)可作為記錄系統(tǒng),可用于具體化域?qū)ο蟆?/p>

這樣一來(lái),無(wú)需同步數(shù)據(jù)模型和業(yè)務(wù)域,從而簡(jiǎn)化復(fù)雜域中的任務(wù),同時(shí)可提高性能、可擴(kuò)展性和響應(yīng)能力。

它還可提供事務(wù)數(shù)據(jù)一致性并保留可啟用補(bǔ)償操作的完整審核記錄和歷史記錄。

命令和查詢責(zé)任分離模式

使用獨(dú)立接口將讀取數(shù)據(jù)的操作與更新數(shù)據(jù)的操作分離。 這可以最大程度地提高性能、可伸縮性和安全性。 通過(guò)提高靈活性,讓系統(tǒng)隨著時(shí)間的推移而改進(jìn);防止更新命令在域級(jí)別引發(fā)并沖突

CQRS 將之前的CRUD 所針對(duì)的一個(gè)DAO對(duì)象拆分成了兩個(gè)對(duì)象,這種分離是基于方法是執(zhí)行命令還是執(zhí)行查詢這一原則來(lái)定的, CUD是命令 Command, R是查詢 Query.

典型示例

以最常用的銀行帳戶應(yīng)用為例, 假設(shè) Account Service 的后臺(tái)存儲(chǔ)為 NoSQL的Cassandra

為避免上述的擴(kuò)展性和一致性的問(wèn)題, 所有帳戶的操作(Create/Update/Delete) 轉(zhuǎn)化為命令, 這里僅以修改(Update) 帳戶余額為例, Create/Delete 也是類似的做法

假設(shè)上月留存 7200 元

  1. command1: +10000 --> 發(fā)工資存入一萬(wàn)
  2. command2: -500 --> 買飯卡花去500
  3. command3: -3000 --> 還信用卡花去3000
  4. command4: +50 --> 基金投資收益 50

Cassandra 的 table 結(jié)構(gòu)如下

CREATE TABLE account_change (
    account_id uuid, 
    change_time timeuuid, 
    change_value int, 
    change_user text, 
    create_time timestamp,
    primary key(account_id, change_time)) 
    with clustering order by(change_time desc)

存儲(chǔ)在 NOSQL 中的鍵值對(duì)如下

insert into account_change(account_id, change_time, change_value, change_user, create_time) 
values(ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9, minTimeuuid('2018-12-18 13:21:20-0500'), 10000, 'alice', '2018-12-18 13:21:20+0800');  

insert into account_change(account_id, change_time, change_value, change_user, create_time) 
values(ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9, minTimeuuid('2018-12-18 14:21:20-0500'), -500, 'bob', '2018-12-18 14:21:20+0800'); 

insert into account_change(account_id, change_time, change_value, change_user, create_time) 
values(ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9, minTimeuuid('2018-12-18 14:21:21-0500'), -3000, 'bob', '2018-12-18 14:21:21+0800');

insert into account_change(account_id, change_time, change_value, change_user, create_time) 
values(ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9, minTimeuuid('2018-12-18 14:21:22-0500'), 500, 'carl', '2018-12-18 14:21:22+0800');

對(duì)于帳戶的查詢可回溯以上命令, 先查詢出此帳戶的更改命令記錄.

Query:

select * from account_change where account_id= ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9;
account_id change_time change_user change_value create_time
ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9 1a6de500-02fa-11e9-8080-808080808080 carl 500 2018-12-18 06:21:22+0000
ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9 19d54e80-02fa-11e9-8080-808080808080 bob -3000 2018-12-18 06:21:21+0000
ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9 193cb800-02fa-11e9-8080-808080808080 bob -500 2018-12-18 06:21:20+0000
ddccb8eb-1e89-4e27-a222-e35dcc7cd5f9 b7785000-02f1-11e9-8080-808080808080 alice 10000 2018-12-18 05:21:20+0000
  • 事件溯源: 將命令逐條取出, 按照時(shí)間(change_time timeuuid) 倒序排列, 回放命令得到以下帳戶余額: 7200+10000-500-3000+50 = 13750

這樣的做法, 比直接逐次將余額修改成 17200, 16700, 13700, 13750 看起來(lái)更麻煩, 其實(shí)在操作上更簡(jiǎn)單, 查詢余額時(shí)雖然多了計(jì)算的步驟, 可相比處理麻煩的競(jìng)態(tài)條件, 維護(hù)強(qiáng)一致性, 這種做法相比起來(lái)更容易實(shí)現(xiàn)。

當(dāng)然,沒有銀彈,沒有萬(wàn)能藥,事件源模式有個(gè)缺點(diǎn),如果事件太多會(huì)追溯太久,性能難以忍受,這時(shí)應(yīng)該適時(shí)存儲(chǔ)快照,或者應(yīng)用具體化視圖模式和補(bǔ)償事務(wù)模式

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容