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