領域設計之倉儲和工廠模式!
?倉儲就類似于倉庫管理員,它是聚合的管理。
倉儲介于領域模型和數據模型之間:
- 主要用于聚合的持久化和檢索。
它隔離了領域模型和數據模型,以便我們關注于領域模型而不需要考慮如何進行持久化。
為什么要用倉儲
解耦領域層和基礎層
DDD嚴格的分層架構告訴我們:
?
每一層只能與其下方的一層發生耦合。
因此用戶接口層只與應用層發生交互,應用層往下只與領域層發生交互,領域層往下只與基礎層發生交互。
在傳統的代碼分層結構Controller—Service—Dao結構中:
?
經常能看到在Service業務實現層的代碼中嵌入SQL,或者在其中頻繁出現修改數據對象并調用
DAO
的情況。
- 這樣的話,基礎層的數據處理邏輯就滲透到了業務邏輯代碼中。
在DDD的分層結構中:
?
如果出現上述情況,則基礎層的數據處理邏輯就滲透到了領域層。
- 領域層中的領域模型就難以聚焦在業務邏輯上,對外層的基礎層產生了依賴。
而一旦涉及到數據邏輯的修改,就要到領域層中去修改代碼。
本文要講的倉儲模式就是用來解耦領域層和基礎層的,降低他們之間的耦合和相互影響。
倉儲模式
倉儲模式包含倉儲接口和倉儲實現:
?
倉儲接口
- 面向領域層提供基礎層數據處理相關的接口。
倉儲實現
- 完成倉儲接口對應的數據持久化相關的邏輯處理。
一個聚合配備一個倉儲,由倉儲完成聚合數據的持久化。
- 領域層邏輯面向倉儲接口編程,聚合內的數據持久化過程為DO(領域對象)轉PO(持久化對象)。
當需要更換數據庫類型,或者更改數據處理邏輯時:
?
我們就可以保持業務邏輯接口不動,只修改倉儲實現,保證了領域層業務邏輯和基礎層邏輯隔離。
倉儲的架構
倉儲要依賴數據庫、內存等具體的實現工具去做真正的持久化。
如下圖所示(圖中連線代表依賴關系):
我們可以把倉儲的行為抽象為基本的接口,然后利用控制反轉。
- 把實現該節點的倉儲注入領域模型的運行態中。
實現了倒置依賴的依賴圖如下:
實現舉例
如下示例為一個訂單聚合中對訂單實體的倉儲模式實現。
訂單DO定義:
/**
* 訂單聚合
*/
public class OrderDO {
//訂單ID
private long id;
//訂單時間
private long orderTime;
}
訂單PO定義:
/**
* 訂單聚合的持久化PO
*/
public class OrderPO {
//訂單ID
private long id;
//訂單時間
private long orderTime;
}
倉儲接口定義:
/**
* 訂單聚合倉儲接口
*/
public interface OrderRepository {
/**
* 添加訂單
*/
void addOrder(OrderPO order);
/**
* 更新訂單
*/
void updateOrder(OrderPO order);
/**
* 根據ID查找訂單PO對象
*/
OrderPO findById(long id);
}
倉儲接口實現:
/**
* 訂單倉儲實現
*/
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);
}
}
訂單領域服務實現:
?
后面基礎層發生了變化,則領域層無需動任何代碼。
- 只要倉儲接口不變,領域層的邏輯就可以一直保持不變,維護了領域層的穩定性。
/**
* 定領域服務聚合類
*/
public class OrderDomainService {
@Resource
private OrderRepository orderRepository;
public void addOrder(OrderPO order) {
orderRepository.addOrder(order);
}
}
Respository(倉儲)與DAO(數據訪問層)的區別
在理解了聚合之后,我們可以知道:
?
DAO 是技術手段,Respository是抽象方式。
DAO只是針對對象的操作,而Respository是針對 聚合 的操作。
DAO的操作方式如下:
?
訂單和和訂單明細都有一個對應的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);
}
}
}
Respository的操作方式如下:
// 訂單和訂單明細構成聚合
public class Order {
List<OrderDetail> orderDetail;
...
}
@Service
@Transactional
public class OrderService {
public void createOrder(Order order) throws Exception {
orderRespository.save(order);
}
}
StackOverFlow中有一個回答,講的很好:
?
工廠模式
DO對象創建時,需要確保聚合根和它依賴的對象同時被創建。
?
如果這項工作交給聚合根來實現,則聚合根的構造函數將變得異常龐大。
所以把通用的初始化DO的邏輯,放到工廠中去實現。
?
通過工廠模式封裝聚合內復雜對象的創建過程,完成聚合根,實體和值對象的創建。
- DO對象創建時,通過倉儲從數據庫中獲取PO對象,通過工廠完成PO到DO的轉換。
工廠中還可以包含DO到PO對象的轉換過程,方便完成數據的持久化。
/**
* Order聚合的工廠
* DO和PO的轉換
*/
public class OrderFactory {
/**
* OrderPO到領域對象的數據初始化
*/
protected Order createOrder(OrderPO orderPO){
Order order = new Order();
order.setId(orderPO.getId());
order.setOrderTime(orderPO.getOrderTime());
return order;
}
/**
* 領域對象到持久化對象PO的轉換
*/
protected OrderPO createOrderPO(Order order){
OrderPO orderPO = new OrderPO();
orderPO.setId(order.getId());
orderPO.setOrderTime(order.getOrderTime());
return orderPO;
}
}
參考資料:
- 《基于DDD和微服務的中臺架構與實現》
- 《架構真經》
- 《領域驅動設計:軟件核心復雜性應對之道》
- 《實現領域驅動設計》