領(lǐng)域設(shè)計(jì)之倉儲(chǔ)和工廠模式!
?倉儲(chǔ)就類似于倉庫管理員,它是聚合的管理。
倉儲(chǔ)介于領(lǐng)域模型和數(shù)據(jù)模型之間:
- 主要用于聚合的持久化和檢索。
它隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進(jìn)行持久化。
為什么要用倉儲(chǔ)
解耦領(lǐng)域?qū)雍突A(chǔ)層
DDD嚴(yán)格的分層架構(gòu)告訴我們:
?
每一層只能與其下方的一層發(fā)生耦合。
因此用戶接口層只與應(yīng)用層發(fā)生交互,應(yīng)用層往下只與領(lǐng)域?qū)影l(fā)生交互,領(lǐng)域?qū)油轮慌c基礎(chǔ)層發(fā)生交互。
在傳統(tǒng)的代碼分層結(jié)構(gòu)Controller—Service—Dao結(jié)構(gòu)中:
?
經(jīng)常能看到在Service業(yè)務(wù)實(shí)現(xiàn)層的代碼中嵌入SQL,或者在其中頻繁出現(xiàn)修改數(shù)據(jù)對(duì)象并調(diào)用
DAO
的情況。
- 這樣的話,基礎(chǔ)層的數(shù)據(jù)處理邏輯就滲透到了業(yè)務(wù)邏輯代碼中。
在DDD的分層結(jié)構(gòu)中:
?
如果出現(xiàn)上述情況,則基礎(chǔ)層的數(shù)據(jù)處理邏輯就滲透到了領(lǐng)域?qū)印?/p>
- 領(lǐng)域?qū)又械念I(lǐng)域模型就難以聚焦在業(yè)務(wù)邏輯上,對(duì)外層的基礎(chǔ)層產(chǎn)生了依賴。
而一旦涉及到數(shù)據(jù)邏輯的修改,就要到領(lǐng)域?qū)又腥バ薷拇a。
本文要講的倉儲(chǔ)模式就是用來解耦領(lǐng)域?qū)雍突A(chǔ)層的,降低他們之間的耦合和相互影響。
倉儲(chǔ)模式
倉儲(chǔ)模式包含倉儲(chǔ)接口和倉儲(chǔ)實(shí)現(xiàn):
?
倉儲(chǔ)接口
- 面向領(lǐng)域?qū)犹峁┗A(chǔ)層數(shù)據(jù)處理相關(guān)的接口。
倉儲(chǔ)實(shí)現(xiàn)
- 完成倉儲(chǔ)接口對(duì)應(yīng)的數(shù)據(jù)持久化相關(guān)的邏輯處理。
一個(gè)聚合配備一個(gè)倉儲(chǔ),由倉儲(chǔ)完成聚合數(shù)據(jù)的持久化。
- 領(lǐng)域?qū)舆壿嬅嫦騻}儲(chǔ)接口編程,聚合內(nèi)的數(shù)據(jù)持久化過程為DO(領(lǐng)域?qū)ο螅┺D(zhuǎn)PO(持久化對(duì)象)。
當(dāng)需要更換數(shù)據(jù)庫類型,或者更改數(shù)據(jù)處理邏輯時(shí):
?
我們就可以保持業(yè)務(wù)邏輯接口不動(dòng),只修改倉儲(chǔ)實(shí)現(xiàn),保證了領(lǐng)域?qū)訕I(yè)務(wù)邏輯和基礎(chǔ)層邏輯隔離。
倉儲(chǔ)的架構(gòu)
倉儲(chǔ)要依賴數(shù)據(jù)庫、內(nèi)存等具體的實(shí)現(xiàn)工具去做真正的持久化。
如下圖所示(圖中連線代表依賴關(guān)系):
我們可以把倉儲(chǔ)的行為抽象為基本的接口,然后利用控制反轉(zhuǎn)。
- 把實(shí)現(xiàn)該節(jié)點(diǎn)的倉儲(chǔ)注入領(lǐng)域模型的運(yùn)行態(tài)中。
實(shí)現(xiàn)了倒置依賴的依賴圖如下:
實(shí)現(xiàn)舉例
如下示例為一個(gè)訂單聚合中對(duì)訂單實(shí)體的倉儲(chǔ)模式實(shí)現(xiàn)。
訂單DO定義:
/**
* 訂單聚合
*/
public class OrderDO {
//訂單ID
private long id;
//訂單時(shí)間
private long orderTime;
}
訂單PO定義:
/**
* 訂單聚合的持久化PO
*/
public class OrderPO {
//訂單ID
private long id;
//訂單時(shí)間
private long orderTime;
}
倉儲(chǔ)接口定義:
/**
* 訂單聚合倉儲(chǔ)接口
*/
public interface OrderRepository {
/**
* 添加訂單
*/
void addOrder(OrderPO order);
/**
* 更新訂單
*/
void updateOrder(OrderPO order);
/**
* 根據(jù)ID查找訂單PO對(duì)象
*/
OrderPO findById(long id);
}
倉儲(chǔ)接口實(shí)現(xiàn):
/**
* 訂單倉儲(chǔ)實(shí)現(xiàn)
*/
public class OrderRepositoryImpl implements OrderRepository {
@Resource
private OrderDao orderDao;
@Override
public void addOrder(OrderPO order) {
orderDao.addOrder(order);
}
@Override
public void updateOrder(OrderPO order) {
orderDao.updateOrder(order);
}
@Override
public OrderPO findById(long id) {
return orderDao.findById(id);
}
}
訂單領(lǐng)域服務(wù)實(shí)現(xiàn):
?
后面基礎(chǔ)層發(fā)生了變化,則領(lǐng)域?qū)訜o需動(dòng)任何代碼。
- 只要倉儲(chǔ)接口不變,領(lǐng)域?qū)拥倪壿嬀涂梢砸恢北3植蛔?,維護(hù)了領(lǐng)域?qū)拥姆€(wěn)定性。
/**
* 定領(lǐng)域服務(wù)聚合類
*/
public class OrderDomainService {
@Resource
private OrderRepository orderRepository;
public void addOrder(OrderPO order) {
orderRepository.addOrder(order);
}
}
Respository(倉儲(chǔ))與DAO(數(shù)據(jù)訪問層)的區(qū)別
在理解了聚合之后,我們可以知道:
?
DAO 是技術(shù)手段,Respository是抽象方式。
DAO只是針對(duì)對(duì)象的操作,而Respository是針對(duì) 聚合 的操作。
DAO的操作方式如下:
?
訂單和和訂單明細(xì)都有一個(gè)對(duì)應(yīng)的DAO。
訂單和訂單明細(xì)的關(guān)系并沒有在對(duì)象之間得到體現(xiàn)。
@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);
}
}
}
Respository的操作方式如下:
// 訂單和訂單明細(xì)構(gòu)成聚合
public class Order {
List<OrderDetail> orderDetail;
...
}
@Service
@Transactional
public class OrderService {
public void createOrder(Order order) throws Exception {
orderRespository.save(order);
}
}
StackOverFlow中有一個(gè)回答,講的很好:
?
工廠模式
DO對(duì)象創(chuàng)建時(shí),需要確保聚合根和它依賴的對(duì)象同時(shí)被創(chuàng)建。
?
如果這項(xiàng)工作交給聚合根來實(shí)現(xiàn),則聚合根的構(gòu)造函數(shù)將變得異常龐大。
所以把通用的初始化DO的邏輯,放到工廠中去實(shí)現(xiàn)。
?
通過工廠模式封裝聚合內(nèi)復(fù)雜對(duì)象的創(chuàng)建過程,完成聚合根,實(shí)體和值對(duì)象的創(chuàng)建。
- DO對(duì)象創(chuàng)建時(shí),通過倉儲(chǔ)從數(shù)據(jù)庫中獲取PO對(duì)象,通過工廠完成PO到DO的轉(zhuǎn)換。
工廠中還可以包含DO到PO對(duì)象的轉(zhuǎn)換過程,方便完成數(shù)據(jù)的持久化。
/**
* Order聚合的工廠
* DO和PO的轉(zhuǎn)換
*/
public class OrderFactory {
/**
* OrderPO到領(lǐng)域?qū)ο蟮臄?shù)據(jù)初始化
*/
protected Order createOrder(OrderPO orderPO){
Order order = new Order();
order.setId(orderPO.getId());
order.setOrderTime(orderPO.getOrderTime());
return order;
}
/**
* 領(lǐng)域?qū)ο蟮匠志没瘜?duì)象PO的轉(zhuǎn)換
*/
protected OrderPO createOrderPO(Order order){
OrderPO orderPO = new OrderPO();
orderPO.setId(order.getId());
orderPO.setOrderTime(order.getOrderTime());
return orderPO;
}
}
參考資料:
- 《基于DDD和微服務(wù)的中臺(tái)架構(gòu)與實(shí)現(xiàn)》
- 《架構(gòu)真經(jīng)》
- 《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道》
- 《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》