Achievement provides the only real pleasure in life
CRUD模式
在 Web Service 中我們一般分為三層:
- Controller
- Service
- Repository
而作用于這三個(gè)層次的對(duì)象有:
- DTO (Data Transfer Object) 數(shù)據(jù)傳輸對(duì)象
- BO (Business Object or Domain Object)
- 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 元
- command1: +10000 --> 發(fā)工資存入一萬(wàn)
- command2: -500 --> 買飯卡花去500
- command3: -3000 --> 還信用卡花去3000
- 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ù)模式