一、解耦領域層和基礎層
DDD嚴格的分層架構告訴我們,每一層只能與其下方的一層發生耦合。因此用戶接口層只與應用層發生交互,應用層往下只與領域層發生交互,領域層往下只與基礎層發生交互。
在傳統的代碼分層結構Controller—Service—Dao結構中,經常能看到在Service業務實現層的代碼中嵌入SQL,或者在其中頻繁出現修改數據對象並調用DAO的情況。這樣,基礎層的數據處理邏輯就滲透到了業務邏輯代碼中。
在DDD的分層結構中,如果出現上述情況,則基礎層的數據處理邏輯就滲透到了領域層,領域層中的領域模型就難以聚焦在業務邏輯上,對外層的基礎層產生了依賴。而一旦涉及到數據邏輯的修改,就要到領域層中去修改代碼,重新調試領域層與基礎層的交互,或者當切換異構數據庫類型時,需要大量修改領域層的代碼,將業務邏輯和數據處理邏輯重新適配(主要在於異構數據庫導致的SQL或數據對象調整),因此技術升級會變得特別麻煩。
本文要講的倉儲模式就是用來解耦領域層和基礎層的,降低他們之間的耦合和相互影響

二、倉儲模式
為了解耦領域邏輯和數據處理邏輯,在中間加了薄薄的一層倉儲。
倉儲模式包含倉儲接口和倉儲實現,倉儲接口面向領域層提供基礎層數據處理相關的接口,倉儲實現則完成倉儲接口對應的數據持久化相關的邏輯處理。一個聚合配備一個倉儲,由倉儲完成聚合數據的持久化。領域層邏輯面向倉儲接口編程,聚合內的數據持久化過程為DO(領域對象)轉PO(持久化對象)。
當需要更換數據庫類型,或者更改數據處理邏輯時,我們就可以保持業務邏輯接口不動,只修改倉儲實現,保證了領域層業務邏輯的干凈和純潔。
如下示例為一個人員聚合中對人員實體的倉儲模式實現:
人員DO和人員PO定義:
/** * 人員聚合 * @author test11 */ public class Person { //人員id private String id; //姓名 private String name; //地址(值對象) private Address address; //上班行為 private void goWork(){ } //下班行為 private void leaveWork(){ } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
/** * 人員聚合的持久化PO * @author test11 */ public class PersonPO { //人員id private String id; //姓名 private String name; //地址 private Address address; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
/** * 地址值對象 */ public class Address { //省份 private String province; //城市 private String city; //街道 private String street; }
倉儲接口定義:
/** * 人員聚合倉儲接口 * @author test11 */ public interface PersonRepository { /** * 添加人員 */ void addPerson(PersonPO personPO); /** * 更新人員 */ void updatePerson(PersonPO personPO); /** * 根據id查找人員PO對象 * @return */ PersonPO findById(String id); }
倉儲接口實現:
/** * 人員倉儲實現 * @author test11 */ public class PersonRepositoryImpl implements PersonRepository{ @Resource PersonDao personDao; @Override public void addPerson(PersonPO personPO) { personDao.addPerson(personPO); } @Override public void updatePerson(PersonPO personPO) { personDao.updatePerson(personPO); } @Override public PersonPO findById(String id) { return personDao.findById(id); } }
人員領域服務實現:后面基礎層發生了變化,則領域層無需動任何代碼,只要倉儲接口不變,領域層的邏輯就可以一直保持不變,維護了領域層的穩定性。領域服務是可以做成企業級可復用的服務的,因此穩定性必須有保障。
import javax.annotation.Resource; /** * 人員領域服務聚合類 * @author test11 */ public class PersonDomainService { @Resource PersonRepository personRepository; public void addPerson(PersonPO personPO) { personRepository.addPerson(personPO); } }
三、工廠模式
DO對象創建時,需要確保聚合根和它依賴的對象同時被創建,如果這項工作交給聚合根來實現,則聚合根的構造函數將變得異常龐大,所以我們把通用的初始化DO的邏輯,放到工廠中去實現,通過工廠模式封裝聚合內復雜對象的創建過程,完成聚合根,實體和值對象的創建。DO對象創建時,通過倉儲從數據庫中獲取PO對象,通過工廠完成PO到DO的轉換
工廠中還可以包含DO到PO對象的轉換過程,方便完成數據的持久化。
/** * Person聚合的工廠 * DO和PO的轉換 * @author test11 */ public class PersonFactory { /** * 人員PO到領域對象的數據初始化 * @param personPO * @return */ protected Person createPerson(PersonPO personPO){ Person person = new Person(); person.setId(personPO.getId()); person.setName(personPO.getName()); person.setAddress(personPO.getAddress()); return person; } /** * 領域對象到持久化對象PO的轉換 * @param person * @return */ protected PersonPO createPersonPO(Person person){ PersonPO personPO = new PersonPO(); personPO.setId(person.getId()); personPO.setName(person.getName()); personPO.setAddress(person.getAddress()); return personPO; } }
參考書籍 ——《基於DDD和微服務的中台架構與實現》歐創新、鄧頔
參考書籍 ——《領域驅動設計》Eric Evans
參考書籍 ——《架構真經》Martin L. Abbott