來源:JoeCao,git
github.com/JoeCao/JoeCao.github.io/issues/4程序員
上篇咱們聊了微服務的DDD之間的關係,不少人仍是以爲很虛幻,DDD那麼複雜的理論,聚合根、值對象、事件溯源,到底咱們該怎麼入手呢?github
實際上DDD和麪向對象設計、設計模式等等理論有千絲萬縷的聯繫,若是不熟悉OOA、OOD,DDD也是使用很差的。不過學習這些OO理論的時候,你們每每感受到無用武之地,由於大部分的Java程序員開發生涯是從學習J2EE經典的分層理論開始的(Action、Service、Dao),在這種分層理論中,咱們基本沒有啥機會使用那些所謂的「行爲型」的設計模式,這裏的核心緣由,就是J2EE經典分層的開發方式是「貧血模型」。數據庫
Martin Fowler在他的《企業應用架構模式》這本書中提出了兩種開發方式「事務腳本」和「領域模型」,這兩種開發分別對應了「貧血模型」和「充血模型」。設計模式
事務腳本開發模式微信
事務腳本的核心是過程,能夠認爲大部分的業務處理都是一條條的SQL,事務腳本把單個SQL組織成爲一段業務邏輯,在邏輯執行的時候,使用事務來保證邏輯的ACID。最典型的就是存儲過程。固然咱們在平時J2EE經典分層架構中,常常在Service層使用事務腳本。架構
使用這種開發方式,對象只用於在各層之間傳輸數據用,這裏的對象就是「貧血模型」,只有數據字段和Get/Set方法,沒有邏輯在對象中。併發
咱們以一個庫存扣減的場景來舉例:函數
業務場景微服務
首先談一下業務場景,一個下訂單扣減庫存(鎖庫存),這個很簡單
先判斷庫存是否足夠,而後扣減可銷售庫存,增長訂單佔用庫存,而後再記錄一個庫存變更記錄日誌(做爲憑證)
貧血模型的設計
首先設計一個庫存表 Stock,有以下字段
設計一個Stock對象(Getter和Setter省略)
public class Stock {
private String spuId;
private String skuId;
private int stockNum;
private int orderStockNum;
}
Service入口
設計一個StockService,在其中的lock方法中寫邏輯。
入參爲 (spuId, skuId, num)
實現僞代碼
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這樣的重型對象引入了這個微服務?兩個重型的對象在一個服務中?這樣的微服務拆不開啊,仍是必須依靠商品庫?!
請關注下一篇,經過事件驅動架構來完成領域間的鬆耦合。
熱門下載
☞【發送1】金融時間序列學習進階資料
☞【發送2】200份重量級商業計劃書
☞【發送3】金融時間序列學習進階資料大禮包
☞【發送4】100本政商領袖必讀書籍
☞【發送5】K8S調度資料打包
本文分享自微信公衆號 - 架構師智庫(beijing-tmt)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。