官术网_书友最值得收藏!

  • 實現領域驅動設計
  • (美)沃恩·弗農
  • 6466字
  • 2020-09-05 00:21:58

前言

所有的計算都表明它不工作,唯一的做法是:使其工作。

——Pierre-Georges Latécoère 早期法國航空企業家

是的,我們將使其工作。然而,在軟件開發過程中采用領域驅動設計卻是困難的。即便是有能力的開發者,也很難找到實現領域驅動設計的正確方法。

起飛,著陸

在我小的時候,我的父親學習過駕駛小型飛機。我們經常會全家出去飛行,有時會飛到另一個機場,在那里吃過午飯后再返回。當父親時間有限而他依然想飛時,父親便帶上我一起在機場上空盤旋,起飛,著陸,再起飛,再著陸。

也會有些長途飛行,這時我們會帶上一張由父親先前繪制好的路線圖。我們幾個小孩便當起了領航員:將圖上的標志對應著陸地上的地標,以確保我們沒有跑偏航線。這是一件很有趣的事情,因為要識別遠在地面上的物體是很有挑戰性的。事實上,我敢肯定父親根本不用我們領航便知道我們處于什么方位——他能看到儀表盤上的所有信息,并且他擁有儀表飛行執照。

空中的景觀的確改變了我的視野。不時地,父親和我會飛過我們鄉下的房子。在幾百英尺的高空中,我體會到了另一種“家”的概念,而這在之前是沒有過的。當我們飛過自家的房子時,母親和我的姐妹們便會跑到院子里向我們揮手。我知道那是她們,即便我看不清楚她們是誰。談話肯定是不行的,連大聲喊都不行,她們是聽不見的。我還可以看到將我家和外面公路分開的護欄,平時我們會像走平衡木一樣在護欄上面走來走去。從空中看,它們就像被細心編排過的小樹枝一樣。我們家的院子很大,每每到了夏天,我都會開著割草機一排一排地修理院子里的草坪。而在空中時,我只能看到一片綠色,小草的葉子肯定是看不清楚的。

我喜歡在空中的時刻,直到現在我還不時回想起這些時刻,好像那個降落飛機的黃昏就發生在不久以前一樣。雖然如此,在地面上的感覺依然是無法取代的,因為它給我一種腳踏實地的感覺。

著陸于領域驅動設計

一開始接觸領域驅動設計(DDD)就像一個小孩之于飛行一樣。天空中的景色是令人驚嘆的,但有時我們卻因為過于陌生而搞不明白它們到底是什么。要從甲地到乙地顯得如此的遙遠。然而,DDD的“成年人”們卻總知道他們所處的方位,因為他們在很早之前便繪制好了路線圖,并且能夠完全按照儀表進行相應的操作。而還有很多人找不到“在地面上”的感覺,此時我們需要的是“穩定著陸”的能力,然后找到一張地圖給我們指引方向。

Eric Evans的《領域驅動設計:軟件核心復雜性應對之道》是一本經得住時間考驗的經典之作。我堅定地相信,在接下來的幾十年里,本書依然會是開發者的實用指導。和其他模式一樣,該書為我們建立起了一種高屋建瓴式的寬闊視野。然而,對于如何實現DDD,我們可能將面對更多的挑戰。通常來說,我們更渴望看到一些具體的例子。

我的目標之一便是幫助你來一個“軟著陸”,保全飛機,然后沿著一條周知的線路帶你回家。這將幫助你如何更好地去實現DDD,并且通過你所熟悉的工具和技術給出示例演示。當然,任何一個人都不可能一直呆在家里,所以我還會帶領你到新的地帶去冒險,這些地帶你可能從來沒有去過。冒險之路是險峻的,但是在正確的戰術應對下,征服這些困難是可能的。在這條冒險之路上,你將學到另外的架構和模式來集成多個領域模型。你將接觸到先前沒有被研究過的集成方法,并且學到如何開發自治性服務。

我將向你提供一張對短途旅行和長途旅行均適用的地圖,它可以幫助你更好地享受沿途風景,同時又不至于迷失途中。

對照地形,繪制飛行圖

在軟件開發的過程中,我們經常做的一件事便是將一種東西映射到另一種東西。我們將對象映射到數據庫,映射到用戶界面,或者映射到不同的應用層展現(包括作為消費方的其他系統或應用程序)。在所有這些映射中,我們很自然地希望在Evans提出的高層模式和具體實現之間存在一種映射。

即便你已經接觸過DDD,你依然有很多可以獲益的地方。有時,DDD首先被看作是一套技術工具集,有人將此稱為DDD-Lite。我們可能已經對實體、服務等DDD概念非常熟悉了,并且大膽地嘗試著設計聚合,還通過資源庫來管理持久化。這些模式是大家相對熟知的,使用起來很容易,我們甚至還使用了值對象。以上這些都屬于戰術設計模式范疇,也即更加偏向技術層面。這些模式可以很好地幫我們解決軟件問題。而同時,對于戰術性模式,我們依然有許多需要學習的。我將戰術模式映射到實現層面。

你曾了解過戰術建模之外的東西嗎?你曾了解過被稱為DDD“另一半”的戰略設計模式嗎?如果你還沒有使用過限界上下文和上下文映射圖,那么你很有可能也沒有使用過通用語言。

如果說Evans在軟件開發社區有一項發明,那便是通用語言。通用語言是一種團隊協作模式,用于捕捉特定業務領域中的概念和術語。一個特定領域的軟件模型通過不同的名詞、形容詞和動詞來表達,這些詞匯是開發團隊正式使用的,而團隊中應該包含一個或多個領域專家。然而,將通用語言僅限定于一些詞匯則是錯誤的。就像自然語言反映人們的思想一樣,DDD的通用語言反映了領域專家對于軟件系統的思維模型。通用語言和那些戰略和戰術性的建模模式同等重要,在有些情況下甚至更具有持久性。

簡單地講,DDD-Lite將導致劣質的領域對象,因為通用語言、限界上下文和上下文映射圖的作用太大了,你從其中獲得的并不只是一套團隊共用的語言。在限界上下文中用通用語言來表述一個領域模型可以增加業務價值,并且使我們確信所開發軟件的正確性。即使從技術的角度,它也可以幫助我們創建更好的領域模型,這樣的模型行為豐滿,業務純凈,并且可以減少犯錯誤的可能性。因此,我將戰略設計模式映射到了可理解的實際例子中。

本書對于DDD的映射可以幫助你同時體會到戰略設計和戰術設計的好處。通過一些具體的例子,你將感受到這些DDD映射的業務價值和技術展現力。

如果我們對于DDD的所有實踐都只是停留在“地面上”,那將是令人失望的。過度地拘泥于細節將使我們喪失在空中俯瞰的機會。所以,不要將自己局限在地面的細節上,要勇敢地飛翔在空中,居高臨下。搭上戰略設計的航班,去了解限界上下文和上下文映射圖,你將獲得更廣闊的視野。當你從DDD的航班中獲益時,我的目的也就達到了。

各章概要

以下是各章的主要內容以及你將如何從中獲益。

第1章:DDD入門

本章向你介紹DDD的好處,并且教你如何盡可能多地去實現DDD。你將學到當你在應對復雜的軟件系統時,DDD可以為你的項目和團隊帶來什么。同時,你將了解到通常的DDD替代方案以及這些方案為什么會導致問題。作為對DDD的基礎講解,本章將教你如何在項目中開始采用DDD,還有如何向你的領域專家和技術團隊推銷DDD。在DDD的武裝下,你將學會如何迎接挑戰,勇往直前。

本章將介紹關于一個公司及其團隊的案例研究,雖然該公司是虛構的,但是他們所面臨的DDD挑戰卻是真實存在的。該公司旨在開發一個新的多租戶SaaS(Software as a Service,軟件即服務)軟件產品。不出所料,在使用DDD時,他們犯了一些常見的錯誤。不過還好,他們發現了這些錯誤,并解決了一些問題,因此項目還算沒有偏離正軌。該團隊需要開發一套基于Scrum的項目管理軟件。該案例還會在本書的后續章節中連續講到。每一種戰略和戰術模式都將教給這個團隊。在這個過程中,團隊有誤入歧途的時候,但最終他們將向著成功的DDD實踐昂首闊步。

第2章:領域、子域和限界上下文

領域、子域和核心域分別是什么?限界上下文是什么,我們為什么要使用它,并且如何使用?這些問題將在這個SaaS項目團隊犯錯誤的時候給予解答。在他們的第一個DDD項目中,他們并不了解子域、限界上下文和通用語言這些概念。事實上,他們根本不知道什么是戰略設計,只是采用了戰術設計來解決一些技術問題。這樣他們在開始設計領域模型的時候便遇到了不少問題。幸運的是,他們及時地意識到了這些問題,項目還有挽回的余地。

本章還講到了如何使用限界上下文對模型進行分離,這是非常重要的;同時還講到了一些模型分離不當的反例,并且給出了有效的實現建議。在采用了這些建議之后,該團隊的成員們重新創建了兩個不同的限界上下文。這種合理的模型分離帶來的好處是引出了第三個限界上下文——核心域,這將是本書使用的主要例子。

對于那些苦于單單從技術層面應用DDD的人來說,本章應該能引起你的共鳴。如果你還是DDD戰略設計的外行,那么本章將為你指明方向。

第3章:上下文映射圖

上下文映射圖幫助我們理解業務領域、模型間的邊界,以及這些模型之間的集成方式。

上下文映射圖絕對不只是繪制系統架構圖這么簡單,它處理的是不同限界上下文之間的關系,以及如何在不同的模型之間映射對象。對于在復雜的業務系統中使用好限界上下文,這是至關重要的。在第2章中,團隊成員們在首次嘗試限界上下文時碰到了問題。本章中,他們將學著如何利用上下文映射圖來解決這些問題。這樣的結果是產生了兩個體面的限界上下文,這兩個上下文將被另外一個負責核心域的團隊所使用。

第4章:架構

我們都知道分層架構,但它是開發DDD軟件的唯一方式嗎,也或許還存在另外的方式?在本章中,我們將講到:六邊形架構(端口和適配器)、面向服務架構、REST、CQRS、事件驅動(管道和過濾器,長時處理過程,事件源)和數據網格,其中好幾種架構都將被該團隊成員所采用。

第5章:實體

在DDD的戰術模式中,我們將首先講到實體。團隊成員們一開始過于強調實體的作用而忽視了值對象。受到數據庫和持久化框架的影響,實體被該團隊濫用了,此時他們開始討論如何避免大范圍地使用實體。

在本章中,你將看到很多優秀的實體設計例子。同時,本章還將講到如何使用實體來表達通用語言,以及如何對實體進行測試、實現和持久化。

第6章:值對象

早些時候,團隊成員們錯過了采用值對象的好機會。他們過于注重為實體創建一些單一的屬性,這種方式是欠妥的,更好的方式是將這些單一的屬性聚合成一個不變的整體。本章將從不同的角度講解如何設計值對象,以及在什么時候采用值對象會優于實體。同時,本章還包含了一些其他話題,比如值對象在集成中的角色和對標準類型的建模等。然后,本章講到了如何設計以領域為中心的測試,如何實現值對象。此外,本章還講到了在聚合中存儲值對象時,如何避免持久化機制所帶來的不利影響。

第7章:領域服務

本章將講到,在領域模型中,什么時候應該將一個概念建模成粒度適中,并且無狀態的領域服務。你將學到何時應該使用領域服務而不是實體或值對象,以及如何使用領域服務來處理業務邏輯和技術上的集成。團隊成員們向我們展示了何時應該使用領域服務,以及如何設計領域服務。

第8章:領域事件

Eric Evans并沒有在他的書中正式介紹領域事件,領域事件是在他那本書出版之后才進入人們視野的。在本章中,你將學到為什么領域事件如此有用,以及使用領域事件的不同方法。領域事件甚至被用來輔助集成和自治性服務。在軟件系統中,我們經常使用一些技術層面的事件機制,但本章將著重講解領域事件與這些事件機制的區別。本章還將指導你如何設計并實現領域事件,包括一些可行的方案和對這些方案的權衡選擇。然后,本章將講到如何創建一個發布-訂閱機制;如何利用事件來集成整個企業軟件中的各個訂閱方;如何創建和管理事件存儲;如何處理消息機制所面臨的常見挑戰等。

第9章:模塊

對于模型中的對象,我們應該如何將他們組織在大小適中的容器中呢?我們又如何保證不同容器中的對象之間只存在有限的耦合?另外,我們如何對這些容器進行命名以體現通用語言?除了包和命名空間之外,我們如何使用由語言和框架提供的現代模塊化機制,比如OSGi和Jigsaw?在本章中,你將看到SaaS團隊成員是如何在不同的項目中使用模塊的。

第10章:聚合

在DDD的戰術模式中,聚合可能是最不容易理解的了。然而,在遵循一定的經驗法則的情況下,我們是能夠更簡單、更快地實現聚合的。在本章中你將學到:如何利用聚合在不同的小規模對象集群間創建一致性邊界,從而降低模型的復雜性。由于在細枝末節上花了太多精力,SaaS團隊成員們在設計聚合時總是磕磕絆絆。我們將仔細研究該團隊所面臨的挑戰,并且分析錯誤的原因以及他們的應對策略。結果,團隊成員們對他們的核心域有了更深層次的理解。我們將看到,在合理的事務處理和保證最終一致性(Eventual Consistency)的前提下,該團隊更正了他們所犯的錯誤,并且在一個分布式環境中設計出了更具有伸縮性和更高效的模型。

第11章:工廠

工廠已經在[Gamma et al.]中被大量地談及了,為什么還要講呢?本章并不打算重蹈覆轍,而是將重點放在“工廠應該存在于何處”這個問題上。在本章中,我們將講到在DDD中實現工廠的技巧。團隊成員在他們的核心域中創建的工廠可以簡化客戶端接口,并且對模型的消費方起到保護作用,從而避免了在多租戶環境中引入災難性的bug。

第12章:資源庫

資源庫只是一個數據訪問對象(Data Access Object,DAO)嗎?如果不是,它們之間有什么區別呢?我們為什么應該將資源庫看成是對集合的模擬而非數據庫呢?在本章中,我們將講到如何利用ORM來實現資源庫,其中有兩種ORM方案,一種采用基于網格的分布式緩存,另一種則采用NoSQL的鍵值對存儲。團隊成員們可以采用任何一種作為他們的持久化機制。

第13章:集成限界上下文

到現在為止,你已經了解了戰略層次的上下文映射圖和多種戰術層次的模式。本章將講到,在DDD中,我們如何通過上下文映射圖來集成不同的模型。在團隊對核心域和其他輔助性的限界上下文進行集成時,我們將給出相應的建議和指導。

第14章:應用程序

對于每一個核心域的通用語言,我們都設計了相應的模型,并且進行了足夠的測試,模型工作正常。然而,客戶應該如何使用我們的模型呢?他們應該使用DTO將數據在模型和用戶界面之間傳輸嗎?或者存在其他方案可以實現模型和展現組件間的數據傳遞?DDD中的應用服務和基礎設施是如何工作的?對于這些問題,本章都將做出解答。

附錄A:聚合與事件源:A+ES

事件源是一種持久化聚合的重要技術,同時也是事件驅動架構的基礎。事件源通過一系列的事件來表示聚合的所有狀態。通過有序的事件重放,我們可以重新構建聚合的狀態。當然,使用事件源的前提是:它能夠簡化對數據的持久化,并且能夠捕捉到那些具有復雜行為屬性的概念。

Java和開發工具

本書中的絕大多數例子都是使用Java語言編寫的。我本來可以用C#的,但是我有意識地使用了Java。

首先,我認為Java社區正在拋棄好的軟件設計和開發實踐。現在,對于多數Java項目而言,要在其中找到一個好的領域對象恐怕是困難的。在我看來,Scrum和敏捷被人們看成了優良設計的替代品,而其中的產品待定項(Product Backlog)被看成了設計本身。多數敏捷人士并不會過多地去思考這些待定項是否會影響到業務模型。我得說明,Scrum的本意絕對不是要取代設計。不管有多少項目經理想將你捆綁在持續交付這條路上,我得說Scrum并不僅僅是要取悅于那些甘特圖(Gantt chart)的追隨者們。然而,太多的時候,情況的確是這樣的。

我認為這是個很大的問題,所以我想鼓勵Java社區重新回到領域建模中來,同時我會通過本書向大家說明,設計是可以使我們獲益的。

此外,在.NET社區中已經有很好的DDD資源了,比如Jimmy Nilsson的《領域驅動設計與模式實戰》[Nilsson]。由于Jimmy的出色工作和其他人對Alt.NET的倡導,.NET社區中正掀起一陣優秀設計的開發浪潮,這是Java社區需要注意的。

其次,我意識到C#.NET人員在理解Java代碼上并不存在什么困難。由于很多DDD社區的人都在使用C#.NET,而本書的早期校對人員也都是C#程序員,但是我從來就沒有收到他們的抱怨。因此,我便不用顧慮這些了。

在我寫這本書時,業內正將目光從關系型數據庫轉向基于文檔和鍵值對的存儲方案。這是有原因的,Martin Fowler將這些存儲方案稱為“面向聚合存儲”。這種命名是恰當的,它很好地描述了在DDD中使用NoSQL的好處。

但是,就我從事咨詢的經驗來看,很多開發者還是認定了關系型數據庫和對象-關系映射。因此我想,NoSQL追隨者們應該能夠理解我在書中包含對象-關系映射的章節。然而,我的確得承認,這可能會招致那些認為存在對象-關系阻抗失配(Object-Relational Impedance)的人的鄙視。這無所謂,對此我表示接受,因為絕大多數人在他們的日常工作中都還得面對這種對象-關系阻抗失配。

當然,在第12章“資源庫”中,我同樣提供了基于文檔的、鍵值對的和數據網格的存儲方案。在多處地方,我都討論到了NoSQL對聚合設計的影響。NoSQL趨勢很有可能持續下去,那些對象-關系型的開發者們應該注意了。在本書中你將看到,我能夠同時理解兩個陣營的觀點,并且對于雙方的觀點我都同意。這些都是技術趨勢所導致的摩擦,而這對于積極的變革是有必要的。

主站蜘蛛池模板: 个旧市| 瓦房店市| 益阳市| 嘉禾县| 拉孜县| 绥滨县| 巴东县| 康定县| 阿瓦提县| 额尔古纳市| 绥江县| 固原市| 宽甸| 集安市| 南康市| 河源市| 台州市| 辽阳县| 社会| 民丰县| 晴隆县| 白朗县| 泰安市| 烟台市| 海林市| 武乡县| 芒康县| 西林县| 逊克县| 姚安县| 衢州市| 微山县| 江孜县| 二连浩特市| 澄迈县| 绥芬河市| 东乡族自治县| 灌阳县| 万年县| 年辖:市辖区| 永兴县|