在從事開發多年以後,你是否會感受本身只是一個業務CRUD Boy,並認爲業務沒有多少技術含量。你是否會陷入業務的泥潭中,各類複雜交錯的業務規則使得代碼開始腐爛開始失控,項目開始變得難以維護,迭代舉步維艱。若是你開始意識到這個問題的話,那麼我十分推薦你開始學習領域驅動設計面向領域建模的設計方式。前端
DDD即Domain Driven Design,翻譯成中文的話就是領域驅動設計,首先咱們應該先理解這裏的領域是什麼意思?假設公司內部正在開發一套電商平臺,而電商平臺中包含了庫存、訂單、商品等核心業務。這些核心業務邏輯其實呈現的就是電商平臺領域。通俗的理解就是一整套體系的業務知識即表明了一個領域。比如在線教育平臺,它須要有一套體系的業務,包括招生、線上教學、課程等內容。咱們將這些業務抽象出領域模型,而這些領域模型表達了產品經理所闡述的業務需求,咱們反覆地用這些領域模型與產品經理進行討論溝通最終肯定初步的領域模型。再使用初步的領域模型指導代碼設計開發。數據庫
簡而言之就是:編程
業務知識 --> 領域模型 --> 項目設計與代碼開發bash
不一樣的電商平臺的核心業務邏輯大都是類似的,這部分領域知識是能夠進行復用,區別在於不一樣公司使用的不一樣的編程語言,不一樣的前端控制框架,數據庫框架。採用領域驅動設計的好處在於項目以領域模型爲核心,而Spring MVC、Struts等前端控制框架或者Hibernate、Mybatis對象數據庫框架屬於外圍技術基礎,領域模型其實並不與這些基礎技術產生耦合,因此在領域模型不變的狀況下,咱們是很容易對咱們的基礎設施進行更換的。markdown
那咱們以前的開發也是有這些業務邏輯支撐?那咱們以前傳統的開發模式爲何不能稱之爲面向領域驅動設計呢?回想一下咱們以前的代碼開發,好比一個實現一個購物車下訂單的功能,咱們根據訂單表的字段,依次用商品字段、金額字段、用戶字段等拼湊出訂單表中的一條記錄,而後寫入到訂單表當中去。其實咱們是面向數據庫在進行開發,以數據庫爲重心,而業務邏輯零散地分佈在各個Service當中去,採用的是面向過程的編程方式而不是面向對象的方式,也就沒有造成一套有機的業務邏輯。框架
而面向領域驅動設計則不同,它以領域爲重心,以剛纔的購物車下訂單功能爲例子。在DDD當中,會將購物車相關的業務邏輯封裝到一個ShoppingCart對象中,並直接調用shoppingCart.takeOrder()下訂單的方法,代碼的重心從生成訂單表中的記錄轉移到購物車對象自己,而具體數據庫中如何生成這條記錄並不屬於咱們的核心業務邏輯,它被下放到基礎設施層,由Repository或者Dao等數據交互對象負責去持久化咱們對領域模型下達的指令所產生的數據庫變化。編程語言
項目中的代碼經過不一樣領域模型之間的配合體現了領域專家的業務意圖,使得系統內部造成一套自運行的有機總體。在以往面向過程式的開發方式,咱們很難讓領域專家或者產品經理直接看懂咱們的業務邏輯,而DDD的優點在於shoppingCart.takeOrder()這種相似白話的方式直接體現出業務含義。而咱們代碼中的領域模型和領域專家口中的領域模型是一致的。甚至咱們能夠在沒有基礎設施技術支持的狀況下,直接建模領域模型開始編寫代碼並測試驗證業務邏輯。微服務
戰術設計和戰略設計是DDD針對局部和總體的設計指導。oop
傳統的開發模式中,咱們常用的是一個JavaBean,其中只有映射到數據庫的字段,並無業務行爲。經過填充這個JavaBean,並在對象外部進行業務邏輯的編寫,如計算訂單的最終金額填充到JavaBean中再交由數據庫映射框架進行持久化。學習
而其實這就是Evans所說的貧血症,由於數據和業務行爲隔離開來,造成一種無機的代碼組成。其實這並不是面向對象的編碼方式,當咱們看到Order對象時,咱們根本不知道它含有計算最終金額的業務邏輯,而這其實就是一種所謂的貧血症引發的失憶症。數據和行爲並無緊密的聯繫到一塊兒。
public static void buyProduct(Long orderId, Double price, Long productId, Float discount) { Order order = new Order(); order.setOrderId(orderId); order.setProductId(productId); order.setDiscount(discount); order.setPrice(price); // 計算最終付款金額 if(discount == 0) { throw new RuntimeException("折扣不可爲0!"); } Double paid = price * discount; order.setPaid(paid); OrderDao.save(order); } 複製代碼
而更好的方式咱們應該經過將數據和業務行爲整合到一個對象中去,讓兩者造成一種有機的代碼組成。在GRASP對象職責中有一個原則就是當一個對象擁有某個方法所需的屬性時,那麼更應該將這個方法放置到這個對象中去,而不是放在其餘地方。
public static void buyProduct_(Long orderId, Double price, Long productId, Float discount) {
Order order = new Order(orderId, price, productId, discount);
order.calculatePaid();
OrderDao.save(order);
}
複製代碼
上面的例子更加符合咱們對於業務的描述,但僅僅強調將業務邏輯封裝到數據對象中去還不夠,咱們還須要經過這些對象之間的協做來進行業務的表達。一個很顯而易見的例子就是關於轉帳的例子。
public static void main(String[] args) { Account a = new Account(5); Account b = new Account(5); double transferMoney = 4; if (a.getMoney() < transferMoney) { throw new RuntimeException("餘額不足!"); } a.setMoney(a.getMoney() - transferMoney); b.setMoney(b.getMoney() + transferMoney); } 複製代碼
更好的作法咱們應該借鑑面向對象的建模方式,將領域知識封裝到帳戶Account模型中去。
public void transfer(Account another, double transferMoney) { if (money < transferMoney) { throw new RuntimeException("餘額不足!"); } money = money - transferMoney; another.setMoney(another.getMoney() + transferMoney); } 複製代碼
A帳戶向B帳戶進行轉帳。
public static void main(String[] args) {
Account a = new Account(5);
Account b = new Account(5);
double transferMoney = 4;
a.transfer(b, transferMoney);
}
複製代碼
咱們經過領域模型之間的協做,呈現出來的代碼就像白話同樣。有主語謂語賓語。主語是A帳戶,謂語是transfer()方法,賓語是B帳戶。這樣一來,代碼的自解釋能力也就很是強了。就算產品經理不懂編程語言的話,看到咱們的代碼的話也能理解其中的業務目的。
那什麼場景纔是適合DDD的場景呢?
在可預見的將來中,項目的業務複雜度會愈來愈高,那就很是適合使用DDD的設計方式。而若是項目所有都是一些很是簡單的增刪查改而不多包含業務知識的話,那真是想D也D不起來,由於DDD的思想就是爲了經過模型來表達領域知識,而領域知識自己就很匱乏的話,表達也就無從談起。
咱們能夠經過和領域專家(產品經理)使用一致的通用語言,利用通用語言抽象出領域模型,在根據這些領域模型進行代碼的落地開發,這樣一來便能更好的在代碼中去體現業務領域知識。咱們須要轉變咱們以往的思惟慣性,少從技術層面考慮,而更應該從業務層面去考慮。個人理解是,DDD是一套基於領域爲核心的面向對象編程的方法論。它主要經過兩種設計來實現DDD。一種是戰術設計,你能夠理解爲在單個微服務中的設計。一種是戰略設計,你能夠理解爲多個微服務之間如何進行協做。
戰術設計側重點在於局部的設計,主要有如下幾個概念:
咱們簡單的講解一下這幾個概念是如何在單個限界上下文(即單個微服務)中進行工做的。聚合由實體、值對象進行組成,它維護了事務的一致性。而聚合又由資源庫進行持久化以及查找或者獲取。而當一些業務規則並不能很好的放入實體或者值對象上時,咱們可使用領域服務。
戰略設計側重點在於總體內的不一樣局部如何協做的設計,主要有如下幾個概念:
限界上下文是一種概念上的邊界,領域模型便工做於其中,也即一個限界上下文對應了咱們設計的一個微服務。而不一樣上下文如何進行溝通的話,則利用上下文映射圖的概念來進行指導開發。
關於DDD的討論很是之多,每一個人的看法都不同,這也是DDD爲何難以流行起來的緣由之一。可是DDD的思想仍是很是值得借鑑的。關於DDD的學習我的很是推薦必定要閱讀原著DDD以及IDDD。
《領域驅動設計:軟件核心複雜性應對之道》 提取碼:6290
《實現領域驅動設計》 提取碼:xl3t