總覽
從廣義上講,領域(Domain)即是一個組織所做的事情以及其中所包含的一切。商業機構通常會確定一個市場,然後在這個市場中銷售產品和服務。每個組織都有它自己的業務範圍和做事方式。這個業務範圍以及在其中所進行的活動便是領域。當你為某個組織開發軟體時,你面對的便是這個組織的領域。這個領域對於你來說應該是明晰的,因為你在這個領域中工作。
有一點需要注意的是,「領域」這個詞可能承載了太多含義。領域既可以表示整個業務系統,也可以表示其中的某個核心域或者支撐子域。
由於「領域模型」包含了「領域」這個詞,我們可能會認為應該為整個業務系統建立一個單一的、內聚的、全功能的式的模型。然而,這並不是我們使用DDD的目標。正好相反,在DDD中,一個領域被分為若干子域,領域模型在限界上下文中完成開發。事實上,在開發一個領域模型時,我們關注的通常只是這個業務系統的某個方面。試圖建立一個全功能的領域模型是非常困難的,並且非常容易失敗。
那麼,既然領域模型不能包含整個業務系統,我們應該如何劃分領域模型?
幾乎所有軟體的領域都包含多個子域,這和軟體系統本身的複雜性沒有太大關係。有時,一個業務系統的成功取決於它所提供的多種功能,而將這些功能分開對待是有好處的。
工作中的子域和限界上下文
很多軟體開發者都認為將所有東西都放在一個系統裡面是一件好事。他們會想:「我對電商系統瞭如指掌,我相信這個系統可以滿足任何人的需求。」這是具有欺騙性的,因為你不管向系統中新增多少功能,你都無法滿足每一個潛在客戶的需求。此外如果不通過子域對軟體系統進行劃分,事情將變得更加繁瑣,因為系統中的各個部分都是緊密聯絡在一起的。
子域並不是一定要做的很大,並且包含很多功能。有時,子域可以簡單到只包含一套演演算法,這套演演算法可能對業務系統非常重要,但是並不包含在核心域之中。在正確實施DDD的情況下,這種簡單的子域可以以模組(Module)的形式從核心域中分離出來,而不需要包含在笨重的子系統元件中。
在實施DDD的時候,我們致力於將限界上下文中領域模型所用到的每一個術語都進行限界劃分。這種限界主要是語言層面上的上下文邊界,也是實現DDD的關鍵。
需要注意的是,一個限界上下文並一定是隻包含在一個子域中,但這是可能的。
一個子域對應一個限界上下文。
將關注點放在核心域上
對於核心域,它是整個業務領域的一部分,也是業務成功的主要促成因素。從戰略層面上講,企業應該在核心域上勝人一籌。在實施DDD的過程中,你將主要關注於核心域。
有時,我們會建立或者購買某個限界上下文來支撐我們的業務。如果這樣的限界上下文對應著業務的某些重要方面,但卻不是核心,那麼它便是一個支撐子域。建立支撐子域的原因在於它們專注於業務的某個方面,否則,如果一個子域被用於整個業務系統,那麼這個子域便是通用子域。我們並不是說支撐子域和通用子域是不重要的,它們是重要的,只是我們對它們的要求沒有核心域那麼高。
戰略設計為什麼重要
如果不瞭解戰略設計的基礎,會將關注點放在實體和值物件上,從而缺少一種更廣闊的視野。這種做法還很有可能導致大泥球架構。
團隊真正需要理解的是他們正在開發的領域、子領域和限界上下文,他們需要戰略建模的思維模式。
現實世界中領域和子域
領域中還同時存在問題空間(problem space)和解決方案空間(solution space)。在問題空間中,我們思考的是業務所面臨的挑戰,而在解決方案空間中,我們思考如何實現軟體以解決這些業務挑戰。以下是如何將這兩者應用到我們已經學過的知識中:
通常,我們希望將子域一對一地對應到限界上下文。這種做法顯式地將領域模型分離到不同的業務板塊中,並將問題空間和解決方案空間融合在一起。在實踐中,這種做法並不總是可能的,但通過新的努力,我們是可以做到這一點。
讓我們考慮一個遺留系統,它有可能是個大泥球,其中子域和限界上下文存在相交的地方。在一個大型的企業中,通過對問題空間的評估,我們可以減少錯誤,進而降低成本。我們可以在概念上使用兩個或者多個子域來分解限界上下文,或者將多個限界上下文包含在同一個子域中。
在我們實施某個解決方案之前,我們需要對問題空間和解決方案空間進行評估。為了保證你的專案朝著正確的方向行進,你需要先回答以下問題:
如果你不瞭解核心域的目標及其所需的支撐子域,那麼你是不能從核心域中得到好處的,同時你也無法避免由此帶來的陷阱。因此,我們需要全面地對問題空間進行評估,並確保所有的利益相關方在核心域的目標上都達成一致。
在理解了問題空間之後,我們來看看解決方案空間。對於問題空間的評估也是有益於理解解決方案空間的。解決方案空間在很大程度上受到現有系統和技術的影響。這裡,我們應該根據分離的限界上下文仔細地思考,考慮以下問題:
請記住,開發核心域的解決方案是一種關鍵性的業務投入。
理解限界上下文
限界上下文是一個顯式的邊界,領域模型便存在於這個邊界之內。領域模型把通用語言表達成軟體模型。建立邊界的原因在於,每一個模型概念,包括它的屬性和操作,在邊界之內都具有特殊的含義。如果你是建模團隊的一員,你便應該知道這些概念的確切含義。
在很多情況下,在不同模型中存在名字相同或相近的物件,但是它們的意思卻是不同。當模型被一個顯式的邊界所包圍時,其中每個概念的含義便是確定的了。因此,限界上下文主要是一個語意上的邊界,我們應該通過這一點來衡量對一個限界上下文的使用正確與否。
當需要整合時,我們必須在不同的限界上下文之間進行概念對映。在DDD中,這可能是複雜的,因此我們應該特別留意。在上下文邊界之外,我們通常不會使用該上下文之內的物件範例,但是不同上下文中彼此關聯的物件可能共用一些狀態。
通常情況下,你是可以識別出那些概念分離正確的情況,因為有些相似的物件擁有不同的屬性和行為,此時我們可以認為上下文邊界的劃分是合理的。然而,如果你在不同的限界上下文中看到了完全相同的物件,這通常意味著你的模型是錯誤的,除非這些限界上下文使用了共用核心。
限界上下文不僅僅只包含模型
一個限界上下文並不是只包含領域模型。誠然,模型是限界上下文的主要「公民」。但是,限界上下文並不只是侷限於容納模型,它通常標定了一個系統、一個應用程式或者一種業務服務。有時,限界上下文所包含的內容可能很少,比如,一個通用子域便可以只包含領域模型。
如果使用者介面被用於渲染模型,並且驅動著模型的行為設計時,同樣,該使用者介面也應該屬於模型所在的上下文邊界之內。但是,這並不表示我們應該在使用者介面中對領域進行建模,因為這樣會導致貧血領域物件。我們應該拒絕使用智慧UI反模式,或者任何試圖將領域概念帶到領域模型之外的舉措。
通常情況下,一個系統/應用程式的使用者並不只是人,還可能是另外的計算機系統。系統中有可能存在諸如Web服務之類的元件。我們也可以使用REST資源來與模型互動,此時的REST資源即被稱為開放主機服務(Open Host Service)。或者,我們可以使用SOAP或訊息伺服器端點。在以上所有情況中,那些面向服務的元件都應該位於上下文邊界之內。
使用者介面或面向伺服器端點都會將操作委派給應用服務。應用服務包含了不同型別的服務,比如安全和事務管理等。對於模型來說,應用服務扮演的是一種門面模式。同時,應用服務還具有任務管理功能,它將來自用例流的請求轉換成領域邏輯的執行流。應用服務也是位於上下文邊界之內的。
限界上下文主要用來封裝通用語言和領域物件,但同時它也包含那些為領域模型提供互動手段和輔助功能的內容。需要注意的是,對於架構中的每個元件,我們都應該將其放在適當的地方。
限界上下文的大小
限界上下文應該足夠大,以能夠表達它對應的整套通用語言。
核心領域之外的概念不應該包含在限界上下文之中。如果一個概念不屬於你的通用語言,那麼一開始你就不應該將其引入到模型中。此外,如果有外部概念進入了你的限界上下文,需要將其清楚,它們可能屬於另外的支撐或者通用子域,或者根本就不屬於某個模型。
請注意,不要將本應該屬於核心域的概念給清楚掉了。你的模型應該能夠完全地展現上下文中的通用語言,而不能遺漏任何重要的概念。此時,我們需要做出正確的判斷,可以藉助諸如上下文對映圖這樣的工具。
使用限界上下文和上下文對映圖這樣的工具可以幫助我們分析出哪些概念的確應該屬於核心域。我們並不隨意地採用非DDD的分離原則。
哪些因素會導致我們建立大小不正確的限界上下文呢?
如果建立限界上下文只是為了架構元件或開發者資源這樣的考慮,那麼此時的通用語言將變得四分五裂,其表達能力也會喪失殆盡。因此我們應該考慮領域專家所講的通用語言,將核心域中的概念自然地組織成單一的限界上下文。這樣一來,你便可以自然地識別出那些單一的、內聚的模型元件。
有時,我們可以使用模組來避免建立一些微小的限界上下文。通過分析分散在不同限界上下文中的服務,你可能會發現,模組可以將多個限界上下文減少到一個。模組也可以用來拆分開發者的任務職責,因此我們可以使用更加戰術化的手段來管理團隊的分配任務。
如果你沒有采用語言驅動,那麼你就不算在和領域專家一起工作來建立限界上下文。認真考慮一下限界上下文的大小,不要急於將其小型化。