第一章:讓模型發揮作用
領域驅動設計是一種開發複雜軟體的方法,我們在其中:
- 專注於 核心領域 。
- 通過領域從業者和軟體從業者的創造性協作探索模型。
- 在明確 有界的上下文中 講一種 無處不在的語言 。
這個DDD的三點摘要取決於術語的定義,這些術語在本手冊中定義。
許多專案最終進行建模工作而沒有獲得太多實際利益。DDD的模式從專案中提煉成功實踐,這些專案從建模中獲得了巨大的好處。總之,他們提出了一種完全不同的建模和軟體開發方法,從精細的細節到高階的願景。嚴格的建模慣例必須與非技術人員合作,自由探索模型。戰術和戰略必須結合起來才能成功,DDD同時解決戰術和戰略設計問題。
有界上下文
任何大型專案都有多種模型可供使用。他們出現的原因有很多。兩套子系統通常服務於非常不同的使用者社群,具有不同的工作,其中不同的模型可能是有用的。獨立工作的團隊由於缺乏溝通其實以不同的方式解決了同樣的問題,工具集也可能不同,這意味著無法共享程式程式碼。
多個模型是不可避免的,但是當基於不同模型的程式碼組合在一起時,軟體變得越來越不可靠,不可靠並且難以理解,團隊成員之間的溝通變得困惑。通常不清楚在什麼上下文下不該應用哪種模型。模型表示式與自然語言中任何其他短語一樣,僅在上下文中具有意義。
因此:
明確定義模型應用的上下文。根據團隊組織,應用程式特定部分的使用情況以及程式碼庫和資料庫模式等物理表現形式明確設定邊界。應用持續整合以保持模型概念和術語在這些範圍內嚴格一致,但不要被外部問題分心或混淆。在上下文中標準化在單個開發過程,不需要在其他地方使用。
無所不在的語言
For first you write a sentence,
And then you chop it small;
Then mix the bits, and sort them out
Just as they chance to fall:
The order of the phrases makes
No difference at all.
-Lewis Carroll,“Poeta Fit,Non Nascitur”
要建立一個靈活的,知識豐富的設計,需要一種多功能,共享的團隊語言,以及在軟體專案中很少使用的語言的生動實驗。
在一個有界的上下文中,語言可能會破壞應用複雜建模的努力。如果該模型僅用於為團隊的技術成員繪製UML圖表,那麼它不會為DDD的核心創意協作做出貢獻。
領域專家使用他們的行話,而技術團隊成員有自己的語言在設計方面討論領域,日常討論的術語與程式碼中嵌入的術語(最終是軟體專案中最重要的產品)脫節,甚至同一個人在言語和寫作中使用不同的語言,因此領域中最精闢的表達通常以一種從未在程式碼中甚至在寫作中捕獲的瞬態形式出現。
翻譯使溝通變得遲鈍,使知識變得貧窮。
然而,這些方言中沒有一種可以成為共同語言,因為沒有一種方言滿足所有需要:
領域專家應該反對那些尷尬或不足以傳達領域理解的術語或結構; 開發人員應注意會導致設計絆倒的模糊或不一致。
在談論系統時,請使用模型。使用模型的元素和互動來大聲描述場景,以模型允許的方式組合概念。找到更簡單的方式來說出您需要說的內容,然後將這些新想法反饋到圖表和程式碼中。
使用無處不在的語言,該模型不僅僅是一個設計工件。它成為開發人員和領域專家共同努力的一部分。
因此:
使用模型作為語言的支柱。讓團隊在團隊內部和程式碼中的所有溝通中堅持不懈地運用該語言。在有界的上下文中,在圖表,書寫,尤其是語音中使用相同的語言。
認識到語言的變化是模型的變化。
通過嘗試反映替代模型的替代表達來消除困難。然後重構程式碼,重新命名類,方法和模組以符合新模型。解決對話中術語的混淆,就像我們就普通詞的含義達成一致一樣。
持續整合
一旦定義了有界上下文,我們必須保持完整。
當許多人在相同的有界上下文中工作時,模型很有可能破碎。團隊越大,問題就越大,但只有三四個人會遇到嚴重的問題。然而,將系統分解為越來越小的上下文最終會失去寶貴的整合和一致性。
因此:
建立一個頻繁合併所有程式碼和其他實現工件的流程,使用自動化測試快速標記碎片。隨著概念在不同人的頭腦中演變,堅持不懈地運用無處不在的語言來敲定模型的共同檢視。
模型驅動設計
將程式碼與基礎模型緊密關聯可以得到程式碼含義並使模型相關。
如果設計或其中心部分未對映到域模型,則該模型價值不大,並且軟體的正確性是可疑的。同時,模型和設計功能之間的複雜對映難以理解,並且在實踐中,隨著設計的變化無法維護。分析與設計之間存在致命的鴻溝,因此在每項活動中獲得的洞察力都不會影響到另一項活動。
從模型中抽取設計中使用的術語和責任的基本分配。程式碼成為模型的表示式,因此對程式碼的更改可能是對模型的更改。它的影響必須相應地影響專案的其餘部分。
將具體實現與模型聯絡起來通常需要軟體開發工具和支援建模範例的語言,例如面向物件的程式設計。
因此:
設計軟體系統的一部分以非常直觀的方式反映域模型,因此對映是顯而易見的。重新審視模型並對其進行修改以便在軟體中更自然地實現,即使您希望使其更深入地瞭解域。除了支援流利的無處不在的語言外,還需要一個能夠很好地滿足這兩個目的的模型。
建模者
如果編寫程式碼的人不對模型負責,或者不瞭解如何使模型適用於應用程式,那麼該模型就會與軟體無關。如果開發人員沒有意識到更改程式碼會改變模型,那麼他們的重構將削弱模型而不是加強模型。同時,當建模者與實施過程分離時,他或她從未獲得或很快失去對實施約束的感覺。模型驅動設計的基本約束 - 模型支援有效實現並將關鍵洞察力抽象到域中 - 已經消失了一半,結果模型將是不切實際的。最後,
因此:
任何為模型做出貢獻的技術人員都必須花一些時間來觸控程式碼,無論他或她在專案中扮演什麼角色。負責更改程式碼的任何人都必須學會通過程式碼表達模型。每個開發人員都必須參與有關模型的某種級別的討論,並與領域專家聯絡。那些以不同方式做出貢獻的人必須有意識地通過普遍存在的語言與那些觸控程式碼的人進行動態的模型思想交流。
banq補充舉例:下面是訂單的模型圖,使用UML表達:
對應的程式碼類如下:
@Table("order_table") public class Order { private Collection<OrderItem> items; private OrderStatus m_OrderStatus; private Address m_Address; @org.springframework.data.annotation.Id private String Id; public Order() { items = new ArrayList<>(); m_OrderStatus = new OrderStatus(0); } public void addItem(OrderItem item) { items.add(item); } public Address getM_Address() { return m_Address; } public void setM_Address(Address m_Address) { this.m_Address = m_Address; } public Collection<OrderItem> getItems() { return items; } public void setItems(Collection<OrderItem> items) { this.items = items; } public OrderStatus getM_OrderStatus() { return m_OrderStatus; } public void switchState() { if (getM_OrderStatus().getState() == 0) { this.m_OrderStatus = new Payment(); } else if (getM_OrderStatus().getState() == 1) { this.m_OrderStatus = new Delivery(); } } public String getId() { return Id; } public void setId(String id) { Id = id; } }
保持這兩者統一,因為他們都是同一個模型的不同表示方法,前者是UML圖,後者是Java程式碼,Java程式碼會因具體資料庫持久方式不同
對模型實現類有所不同,比如這裡使用Spring-data-jdbc,而不是JPA,注意到Order類上面的@Table() 和@org.springframework.data.annotation.Id,在具體後續實現中,開發人員可能會修改Order模型程式碼,這需要及時反映到Order的UML模型圖那裡。不同的技術框架有不同的技術影響和汙染,下圖是Order模型在Spring Boot中微服務實現結構:
重構更深入的洞察力
使用經過驗證的基本構建塊以及一致的語言為開發工作帶來了一些理智。這留下了真正找到一個精闢模型的挑戰,這個模型捕捉到領域專家的微妙關注,並可以推動實際設計。一個模糊的表面和捕捉必要的模型是一個深層模型。這應該使軟體更符合領域專家的思維方式,並更好地響應使用者的需求。
傳統上,重構是根據具有技術動機的程式碼轉換來描述的。重構也可以通過洞察域以及模型的相應細化或其在程式碼中的表達來激發。
複雜的域模型很少有用,除非通過重構的迭代過程開發,包括域專家與有興趣瞭解域的開發人員的密切參與。