標籤:銷售 開發模式 機制 tor date 物件導向 vat 持久 inventory
上篇我們聊了微服務的DDD之間的關係,很多人還是覺得很虛幻,DDD那麼複雜的理論,彙總根、值對象、事件溯源,到底我們該怎麼入手呢?
實際上DDD和物件導向設計、設計模式等等理論有千絲萬縷的聯絡,如果不熟悉OOA、OOD,DDD也是使用不好的。不過學習這些OO理論的時候,大家往往感覺到無用武之地,因為大部分的Java程式員開發生涯是從學習J2EE經典的分層理論開始的(Action、Service、Dao),在這種分層理論中,我們基本沒有啥機會使用那些所謂的“行為型”的設計模式,這裡的核心原因,就是J2EE經典分層的開發方式是“貧血模型”。
Martin Fowler在他的《公司專屬應用程式架構模式》這本書中提出了兩種開發方式“事務指令碼”和“領域模型”,這兩種開發分別對應了“貧血模型”和“充血模型”。
事務指令碼開發模式
事務指令碼的核心是過程,可以認為大部分的業務處理都是一條條的SQL,事務指令碼把單個SQL組織成為一段商務邏輯,在邏輯執行的時候,使用事務來保證邏輯的ACID。最典型的就是預存程序。當然我們在平時J2EE經典分層架構中,經常在Service層使用事務指令碼。
使用這種開發方式,對象只用於在各層之間傳輸資料用,這裡的對象就是“貧血模型”,只有資料欄位和Get/Set方法,沒有邏輯在對象中。
我們以一個庫存扣減的情境來舉例:
業務情境
首先談一下業務情境,一個下訂單扣減庫存(鎖庫存),這個很簡單
先判斷庫存是否足夠,然後扣減可銷售庫存,增加訂單佔用庫存,然後再記錄一個庫存變動記錄日誌(作為憑證)
貧血模型的設計
首先設計一個庫存表 Stock,有如下欄位
設計一個Stock對象(Getter和Setter省略)
1
2
3
4
5
6
public class Stock {
private String spuId;
private String skuId;
private int stockNum;
private int orderStockNum;
}
Service入口
設計一個StockService,在其中的lock方法中寫邏輯
入參為(spuId, skuId, num)
實現虛擬碼
1
2
3
4
5
6
7
count = select stocknum from stock where spuId=xx and skuid=xx
if count>num {
update stock set stocknum=stocknum-num, orderstocknum=orderstocknum+num where skuId=xx and spuId=xx
} else {
//庫存不足,扣減失敗
}
insert stock_log set xx=xx, date= new Date()
ok,打完收工,如果做的好一些,可以把update和select count合一,這樣可以利用一條陳述式完成自旋,解決並發問題(高手)。
小結一下:
有沒有發現,在這個業務領域非常重要的核心邏輯 — 下訂單扣減庫存中操作過程中,Stock對象根本不用出現,全部是資料庫操作SQL,所謂的商務邏輯就是由多條SQL構成。Stock只是CRUD的資料對象而已,沒邏輯可言。
馬丁福勒定義的“貧血模型”是反模式,面對簡單的小系統用事務指令碼方式開發沒問題,商務邏輯複雜了,商務邏輯、各種狀態散布在大量的函數中,維護擴充的成本一下子就上來,貧血模型沒有實施微服務的基礎。
雖然我們用Java這樣的物件導向語言來開發,但是其實和過程型語言是一樣的,所以很多情況下大家用資料庫的預存程序來替代Java寫邏輯反而效果會更好,(ps:用了Spring boot也不是微服務),
領域模型的開發模式
領域模型是將資料和行為封裝在一起,並與現實世界的業務對象相映射。各類具備明確的職責劃分,使得邏輯分散到合適對象中。這樣的對象就是“充血模型” 。
在具體實踐中,我們需要明確一個概念,就是領域模型是有狀態的,他代表一個實際存在的事物。還是接著上面的例子,我們設計Stock對象需要代表一種商品的實際庫存,並在這個對象上面加上商務邏輯的方法
這樣做下單鎖庫存商務邏輯的時候,每次必須先從Repository根據主鍵load還原Inventory這個對象,然後執行對應的lock(num)方法改變這個Inventory對象的狀態(屬性也是狀態的一種),然後再通過Repository的save方法把這個對象持久化到儲存去。
完成上述一系列操作的是Application,Application對外提供了這種整合操作的介面
領域模型開發方法最重要的是把扣減造成的狀態變化的細節放到了Inventory對象執行,這就是對商務邏輯的封裝。
Application對象的lock方法可以和事務指令碼方法的StockService的lock來做個對比,StockService是完全掌握所有細節,一旦有了變化(比如庫存為0也可以扣減),Service方法要跟著變;而Application這種方式不需要變化,只要在Inventory對象內部計算就可以了。代碼放到了合適的地方,計算在合適層次,一切都很合理。這種設計可以充分利用各種OOD、OOP的理論把商務邏輯實現的很漂亮。
充血模型的缺點
從上面的例子,在Repository的load 到執行業務方法,再到save回去,這是需要耗費一定時間的,但是這個過程中如果多個線程同時請求對Inventory庫存的鎖定,那就會導致狀態的不一致,麻煩的是針對庫存的並發不僅難處理而且很常見。
貧血模型完全依靠資料庫對並發的支撐,實現可以簡化很多,但充血模型就得自己實現了,不管是在記憶體中通過鎖對象,還是使用Redis的遠程鎖機制,都比貧血模型複雜而且可靠性下降,這是充血模型帶來的挑戰。更好的辦法是可以通過事件驅動的架構來取消並發。
領域模型和微服務的關係
上面講了領域模型的實現,但是他和微服務是什麼關係呢?在實踐中,這個Inventory是一個限界內容相關的彙總根,我們可以認為一個彙總根就是一個微服務進程。
不過問題又來了,一個庫存的Inventory一定和商品資訊是有關聯的,僅僅靠Inventory中的冗餘那點商品ID是不夠的,商品的上下架狀態等等都是商務邏輯需要的,那不是又把商品Sku這樣的重型對象引入了這個微服務?兩個重型的對象在一個服務中?這樣的微服務拆不開啊,還是必須依靠商品庫?!
請參考下一篇,通過事件驅動架構來完成領域間的松耦合。
}
ok,打完收工,如果做的好一些,可以把update和select count合一,這樣可以利用一條陳述式完成自旋,解決並發問題(高手)。小結一下:有沒有發現,在這個業務領域非常重要的核心邏輯 — 下訂單扣減庫存中操作過程中,Stock對象根本不用出現,全部是資料庫操作SQL,所謂的商務邏輯就是由多條SQL構成。Stock只是CRUD的資料對象而已,沒邏輯可言。馬丁福勒定義的“貧血模型”是反模式,面對簡單的小系統用事務指令碼方式開發沒問題,商務邏輯複雜了,商務邏輯、各種狀態散布在大量的函數中,維護擴充的成本一下子就上來,貧血模型沒有實施微服務的基礎。雖然我們用Java這樣的物件導向語言來開發,但是其實和過程型語言是一樣的,所以很多情況下大家用資料庫的預存程序來替代Java寫邏輯反而效果會更好,(ps:用了Spring boot也不是微服務),領域模型的開發模式領域模型是將資料和行為封裝在一起,並與現實世界的業務對象相映射。各類具備明確的職責劃分,使得邏輯分散到合適對象中。這樣的對象就是“充血模型” 。在具體實踐中,我們需要明確一個概念,就是領域模型是有狀態的,他代表一個實際存在的事物。還是接著上面的例子,我們設計Stock對象需要代表一種商品的實際庫存,並在這個對象上面加上商務邏輯的方法這樣做下單鎖庫存商務邏輯的時候,每次必須先從Repository根據主鍵load還原Inventory這個對象,然後執行對應的lock(num)方法改變這個Inventory對象的狀態(屬性也是狀態的一種),然後再通過Repository的save方法把這個對象持久化到儲存去。完成上述一系列操作的是Application,Application對外提供了這種整合操作的介面領域模型開發方法最重要的是把扣減造成的狀態變化的細節放到了Inventory對象執行,這就是對商務邏輯的封裝。Application對象的lock方法可以和事務指令碼方法的StockService的lock來做個對比,StockService是完全掌握所有細節,一旦有了變化(比如庫存為0也可以扣減),Service方法要跟著變;而Application這種方式不需要變化,只要在Inventory對象內部計算就可以了。代碼放到了合適的地方,計算在合適層次,一切都很合理。這種設計可以充分利用各種OOD、OOP的理論把商務邏輯實現的很漂亮。充血模型的缺點從上面的例子,在Repository的load 到執行業務方法,再到save回去,這是需要耗費一定時間的,但是這個過程中如果多個線程同時請求對Inventory庫存的鎖定,那就會導致狀態的不一致,麻煩的是針對庫存的並發不僅難處理而且很常見。貧血模型完全依靠資料庫對並發的支撐,實現可以簡化很多,但充血模型就得自己實現了,不管是在記憶體中通過鎖對象,還是使用Redis的遠程鎖機制,都比貧血模型複雜而且可靠性下降,這是充血模型帶來的挑戰。更好的辦法是可以通過事件驅動的架構來取消並發。領域模型和微服務的關係上面講了領域模型的實現,但是他和微服務是什麼關係呢?在實踐中,這個Inventory是一個限界內容相關的彙總根,我們可以認為一個彙總根就是一個微服務進程。不過問題又來了,一個庫存的Inventory一定和商品資訊是有關聯的,僅僅靠Inventory中的冗餘那點商品ID是不夠的,商品的上下架狀態等等都是商務邏輯需要的,那不是又把商品Sku這樣的重型對象引入了這個微服務?兩個重型的對象在一個服務中?這樣的微服務拆不開啊,還是必須依靠商品庫?!請參考下一篇,通過事件驅動架構來完成領域間的松耦合。
多研究些架構,少談些架構( 2 ):微服務和充血模型