DDD - 聚合与聚合根_如何理解 Respository与DAO
文章目錄
- Pre
- Question
- 如何理解 聚合和聚合根
- 利用聚合解決業(yè)務(wù)上的原子性操作
- 如何確定聚合和聚合根
- Respository VS DAO
Pre
通常情況,我們都會面臨這樣的一個問題: 架構(gòu)圖說的是一回事,代碼說的卻是另一回事 。 當(dāng)然了這里面的影響因素很多,有一個原因就是某些約束沒有在設(shè)計(jì)中體現(xiàn)出來,也就是說設(shè)計(jì)的表現(xiàn)力不夠 , 而這些約束需要閱讀代碼才能夠知道,這就增加了理解和使用這個組件的難度。
這個問題在基于數(shù)據(jù)建模的設(shè)計(jì)方法上比較明顯, 舉個例子:
DDD - 如何理解Entity與VO提到的購物場景 ,我們以數(shù)據(jù)驅(qū)動的方式來設(shè)計(jì)訂單和產(chǎn)品表,
CREATE TABLE `order` (`rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`seller_id` BIGINT(11) NOT NULL COMMENT '賣家',`buyer_id` BIGINT(11) NOT NULL COMMENT '買家',`price` BIGINT(11) NOT NULL COMMENT '訂單總價格,按分計(jì)算',...PRIMARY KEY (`rec_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;CREATE TABLE `order_detail` (`rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`order_id` BIGINT(11) NOT NULL COMMENT '訂單主鍵',`product_name` VARCHAR(50) COMMENT '產(chǎn)品名稱',`product_desc` VARCHAR(200) COMMENT '產(chǎn)品描述',`product_price` BIGINT(11) NOT NULL COMMENT '產(chǎn)品價格,按分計(jì)算',...PRIMARY KEY (`rec_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;從表關(guān)系上,只能知道order與order_detail是一對多的關(guān)系。
CREATE TABLE `product` (`rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`name` VARCHAR(50) COMMENT '產(chǎn)品名稱',`desc` VARCHAR(200) COMMENT '產(chǎn)品描述',...PRIMARY KEY (`rec_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;CREATE TABLE `product_comment` (`rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`product_id` BIGINT(11) NOT NULL COMMENT '產(chǎn)品',`cont` VARCHAR(2000) COMMENT '評價內(nèi)容',...PRIMARY KEY (`rec_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
從表關(guān)系上,也只能知道product與product_comment之間是一對多的關(guān)系
Question
Q: order與order_detail之間的關(guān)系與product與product_comment之間的關(guān)系是一樣的嗎 ?
這mmp, 單單從數(shù)據(jù)模型上完全區(qū)分不出來啊 ,那只能看下業(yè)務(wù)代碼
@Service @Transactional public class OrderService {public void createOrder(Order order,List<OrderDetail> orderDetailList) throws Exception {// 保存訂單// 保存訂單詳情}} }@Service @Transactional public class ProductService {public void createProduct(Product prod) throws Exception {// 保存產(chǎn)品}} }- 訂單和訂單明細(xì)是一起保存的,也就是說兩者可以作為一個整體來看待 (這個整體就是我們說的聚合)
- 產(chǎn)品和產(chǎn)品評論之間并不能被看做一個整體,所以沒有在一起進(jìn)行操作
這層邏輯,光看上面的設(shè)計(jì)是看不出來的,只有看到代碼了,才能理清這一層關(guān)系 , 無形中就增加了理解和使用難度。
「聚合」就是緩解這種問題的一種手段!
如何理解 聚合和聚合根
public class Artisan {public void say() {System.out.println("1");System.out.println("2");} }對于上面的代碼,如何保障在多線程情況下1和2能按順序打印出來?最簡單的方法就是使用synchronized關(guān)鍵字進(jìn)行加鎖操作
public class Artisan {public synchronized void say() {System.out.println("1");System.out.println("2");} }synchronized保證了代碼的原子性執(zhí)行. 就像 事務(wù)保證了原子性操作一樣。
但是,這和「聚合」有什么關(guān)系呢?
如果說,synchronized是多線程層面的鎖;事務(wù)是數(shù)據(jù)庫層面的鎖,那么「聚合」就是業(yè)務(wù)層面的鎖!
在業(yè)務(wù)邏輯上,有些對象需要保持操作上的原子性,否則就沒有任何意義。這些對象就組成了「聚合」!
利用聚合解決業(yè)務(wù)上的原子性操作
對于上面的訂單與訂單詳情,從業(yè)務(wù)上來看,訂單與訂單明細(xì)需要保持業(yè)務(wù)上的原子性操作:
- 訂單必須要包含訂單明細(xì)
- 訂單明細(xì)必須要屬于某個訂單
- 訂單和訂單明細(xì)被視為一個整體,少了任何一個都沒有意義
所以其對象模型可以表示為:
- 訂單和訂單明細(xì)組成一個「聚合」
- 訂單是操作的主體,所以訂單是這個「聚合」的「聚合根」
- 所有對這個「聚合」的操作,只能通過「聚合根」進(jìn)行
相應(yīng)的,產(chǎn)品和產(chǎn)品評價就不構(gòu)成「聚合」。雖然在表設(shè)計(jì)時,訂單和訂單明細(xì)的結(jié)構(gòu)關(guān)系與產(chǎn)品與產(chǎn)品評價的結(jié)構(gòu)關(guān)系是一樣的!因?yàn)?#xff1a;
- 雖然產(chǎn)品評價需要屬于某個產(chǎn)品
- 但是產(chǎn)品不一定就有產(chǎn)品評價
- 產(chǎn)品評價可以獨(dú)立操作
所以產(chǎn)品與產(chǎn)品評論的模型則可以表示為:
- 產(chǎn)品和產(chǎn)品評論是兩個「聚合」
- 產(chǎn)品評論通過productId與「產(chǎn)品聚合」進(jìn)行關(guān)聯(lián)
如何確定聚合和聚合根
對象在業(yè)務(wù)邏輯上是否需要保證原子性操作是確定聚合和聚合根的其中一個約束。
還有一個約束就是「邊界」,即聚合多大才合適?過大的「聚合」會帶來各種問題。
還是以鎖舉例,看下面的代碼
public class Artisan{public synchronized void say() {System.out.println("0");System.out.println("1");System.out.println("2");System.out.println("4");} }只希望12能按順序打印出來,而0和4沒有這個要求!上面的代碼能滿足要求,但是影響了性能。優(yōu)化方式是使用同步塊,縮小同步范圍:
public class Artisan{public void say() {System.out.println("0");synchronized(Locker.class){System.out.println("1");System.out.println("2");}System.out.println("4");} }「邊界」就像上面的同步塊一樣,只將需要的對象組合成聚合!
假設(shè)上面的產(chǎn)品和產(chǎn)品評論構(gòu)成了一個聚合!那會發(fā)生什么事情呢?當(dāng)A,B兩個用戶同時對這個商品進(jìn)行評論,A先開始評論,此時就會鎖定該產(chǎn)品對象以及下面的所有評論,在A提交評論之前,B是無法操作這個產(chǎn)品對象的,顯然這是不合理的。
Respository VS DAO
在理解了聚合之后,就可以很容易的區(qū)分Respository與DAO了
- DAO是技術(shù)手段,Respository是抽象方式
- DAO只是針對對象的操作,而Respository是針對「聚合」的操作
【DAO的操作方式】
@Service @Transactional public class OrderService {public void createOrder(Order order,List<OrderDetail> orderDetailList) throws Exception {Long orderId = orderDao.save(order);for(OrderDetail detail : orderDetailList) {detail.setOrderId(orderId);orderDetailDao.save(detail);}}} }- 訂單和和訂單明細(xì)都有一個對應(yīng)的DAO
- 訂單和訂單明細(xì)的關(guān)系并沒有在對象之間得到體現(xiàn)
【Respository的操作方式】
// 訂單和訂單明細(xì)構(gòu)成聚合 public clas Order{List<OrderDetail> itemLine; // 這里就保證了設(shè)計(jì)與編碼的一致性... }@Service @Transactional public class OrderService {public void createOrder(Order order) throws Exception {orderRespository.save(order);//ororder.save(); // 內(nèi)部調(diào)用orderRespository.save(this);} }當(dāng)然,orderRespository的save方法中,可能還是數(shù)據(jù)庫相關(guān)操作,但也可能是NoSql操作甚至內(nèi)存操作。
總結(jié)
以上是生活随笔為你收集整理的DDD - 聚合与聚合根_如何理解 Respository与DAO的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DDD - 如何理解Entity与VO
- 下一篇: 深入理解分布式技术 - 分布式缓存实战_