天天看點

領域驅動設計之代碼優先-架構描述 (翻譯)

Microsoft – Spain團隊有一個很不錯的“面向領域多層分布式項目”案例:Microsoft – Domain Oriented N-Layered .NET 4.0 App Sample(在本系列文章中,我使用NLayerApp作為該項目的名稱進行介紹),在codeplex上的位址是:http://microsoftnlayerapp.codeplex.com/。它是學習領域驅動設計(DDD)的一個非常不錯的案例項目。

該文章翻譯自項目的使用者手冊~

1.-  N層應用架構

1.1.- 層(Layers)vs 層 (Tiers)

  這兩個詞在業界曆史上都是廣為采用并且可以替換,但是我們覺得區分它們的是有用的。

  從我們的角度來看,區分Layers和Tiers的概念是很重要的。

  Layers指的是元件和功能子產品的劃分,而不是在不同伺服器或者地方的元件的實體劃分。相反,Tiers指的是

元件和功能子產品在不同伺服器上的實體分布,包括網絡拓撲和遠端地點。雖然Layers和Tiers用了相同的層級的

名字(表現,服務,業務和資料),最好别把它們弄錯了。請記住隻有Tiers表示實體分隔,場用來表示實體分布

模式,例如“2層”、“3層”、“多層”。

  下面我們展示一個3層(Tier)方案和多層(Layer)方案的圖,來看一下剛才讨論的分别:

領域驅動設計之代碼優先-架構描述 (翻譯)

  最後,需要注意的是,因為所有的應用都有一定的複雜性,是以都應該用多層Layer的邏輯架構來進行邏輯建構;

然而,不是所有的應用都應該使用多Tier模式,除非需要Tier上的實體劃分,這在web應用裡很常見。

  3Tier架構已經很老了,但是依然可以應用。當然,内部技術已經發生很大的變化。實際上,3Tier的圖是微軟

大約在1998年的一張圖。

1.2.-  層 (Layers)

背景

設計有相當數量不同抽象級别上元件的複雜商業應用。

問題

問題在于怎樣建構一個支撐複雜操作需求的應用,具有高可維護性,可重用性,可擴充性,健壯并且安全。

相關問題

建構應用時,應該考慮下面的的要求:

-  解決方案中的一部分改變時應該對其他部分應該影響最小,減少修複缺陷的工作量,提高應用的可維護性

和整體的靈活性。

-  職責分離(例如分離使用者界面和業務邏輯,業務邏輯和資料庫通路)也可以增加靈活性、可維護性和可擴充性。

-  為了確定穩定性和品質,每一層必須有單元測試。

-  在應用的不同子產品或者不同的應用裡,某些元件必須是可複用的。

-  開發團隊可以開發解決方案的不同部分,并不依賴于其他團隊的部分,為此,應該通過明确的接口來互動。

-  單獨的元件必須高内聚。

-  不是直接關聯的元件必須松耦合。

-  解決方案的不同元件可以在不同的時間獨立部署、維護、更新。

  層(Layers)被視為構成應用或服務的水準堆疊的一組邏輯上的元件。它們幫助區分完成不同任務的元件,提供

一個最大化複用和可維護性的設計。簡言之,是關于在架構方面應用關注點分離的原則。

  每個頂級的邏輯層可以有若幹個子層,每個子層執行特定的任務。大部分解決方案中通過在子層中使用通用的組

件,建立被承認的模式。我們可以使用這種模式作為我們設計的模型。

  劃分應用中單獨的有不同角色和功能的層增加可維護性。這也允許不同類型的部署,同時提供了一個各種類型

功能子產品和技術的清晰的劃分。

  層的基本設計

  首先,請記住當提到層的基本設計時,我們不是講的DDD多層架構。我們講的是傳統的多層架構(比DDD多層架構簡單)。

  正如已經陳述的,每個解決方案的元件必須分隔到不同的層。每層的元件必須内聚而且有大約相同的抽象級别。每個

一級層應該和其他的一級層松耦合:

  從最底層的抽象級别看,例如第1層。這是系統的基礎層。這些抽象的步驟是一步一步的最後到最頂層。

領域驅動設計之代碼優先-架構描述 (翻譯)

  多層應用的關鍵在于對依賴的管理。傳統的多層架構,層内的元件隻能和同級或者低級層的元件互動。這有利于

減少不同層内元件的依賴。通常有兩種多層架構的設計方法:嚴格和靈活的。

  “嚴格的層設計”限定層内的元件隻能和同一層、或者下一層的元件通信。在上圖中,如果我們用這種設計,第N層

隻能和第N-1層互動,N-1層隻能和N-2層互動,等等。

  “靈活的層設計”允許層内的元件和任何低級别層互動。這種設計中,第N層可以和N-1,N-2層互動。

  這種設計由于不需要對其他層進行重複的調用,進而可以提高性能。然而,這種設計不提供層之間的同層隔離級别,

使得它難以在不影響多個進階層的時候替換一個低級的層。

  在大型複雜的解決方案裡,需要引入多個軟體元件,在同一級别的層(抽象)裡有許多元件是很常見。這樣,不是

内聚的。在本例中,每一層必須被分隔成兩個或者更多的内聚子系統叫做子產品,垂直分布在每個同級層。子產品的概念

會在這章中的後面作為建議架構的一部分介紹。

  下面的UML圖展示了層的組成,相應地在多個子系統内的情況。

領域驅動設計之代碼優先-架構描述 (翻譯)

測試的注意事項

  在正确實作測試後,多層應用極大的提高了能力。

- 由于層之間是通過定義明确的接口進行互動這一事實,很容易為各層添加替代的實作(例如 Mock  and Stubs)。

這使得對一個層的單元測試可以在其依賴層沒完成的情況下進行,或者是要更快的執行一個大型的單元測試,但是

通路依賴層時很大程式的減慢了執行速度。而且,用mocks and stub隔離層元件限制了測試成功或失敗的原因。

是以我們可以在不考慮外部因素的情況下真正的測試内部邏輯。這是真正的單元測試。不同于其他,我們将進行

內建測試。如果我們用基類("分層的超類型“模式)和基接口(”抽象接口“模式),由于它們進一步限制了層間的

依賴,這種能力會增強,由于接口提供了更先進的解耦技術,是以使用接口非常重要,将在後面介紹。

-  因為高層的元件隻能和底層的互動,在單獨的元件上進行測試是很容易的。這有助于分離單獨的元件進行正确的

測試,可以更容易的更改低級層的元件而對應用影響很小(隻要滿足了接口要求)。

使用層的好處 

-  功能容易确定位置,解決方案也就容易維護。層内高内聚,層間松耦合使得維護/組合層更容易。

-  其他的解決方案可以重用由不同層暴露的功能。

-  當項目按邏輯分層時,分布式的部署更容易實作。

-  把層分布到不同的實體層可以提高可伸縮性;然後這一步應該進行仔細的評估,因為可能對性能帶來負面影響。

參考

Buschmann, Frank; Meunier, Regine; Rohnert, Hans; Sommerland, Peter; and Stal, 

Michael. Pattern-Oriented Software Architecture, Volume 1: A System of Patterns. 

Wiley & Sons, 1996. 

Fowler, Martin. Patterns of Application Architecture. Addison-Wesley, 2003. 

Gamma, Eric; Helm, Richard; Johnson, Ralph; and Vlissides, John. Design 

Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.

1.3.-  應該遵從對基本設計原則

  設計系統時,一些基本對設計原則可以幫你建構一個經過實踐驗證的架構。下面的重要原則有助于減少維護費用,

最大限度地提高可用性并且提高可擴充性。

1.3.1.-  "SOLID‟ 設計原則

  SOLID是從下面對短語/原則的英文首字母:

領域驅動設計之代碼優先-架構描述 (翻譯)

我們總結了以下的設計原則:

  單一職責原則:每個類都應該有一個唯一的職責或主要特征。就一個類而言,應該僅有一個引起它變化的原因。

這個原則的一個結果就是,通常類應該盡量不依賴其他對類。

  開閉原則:一個類必須對擴充開放,同時拒絕修改。即,不需要修改類的代碼就可以擴充類的行為。

  裡氏代換原則:子類必須可以被它對基類替換。這源于基于抽象的程式行為不應該随具體的實作而改變的事實。

程式應該使用抽象,而不是具體的實作。我們将在後面看到這條原則與依賴注入和替換實作同一接口的類緊密相關。

  接口分離原則:類的接口實作不應該強制實作無用的方法。這意味着接口應該具體取決于使用它們的類,應該使用

小的接口而不是一個大的接口。針對不同的消費類,類應暴露不同的接口以提供不同的接口要求。

  依賴倒置原則:抽象不應該依賴于細節,細節應該依賴于抽象。類之間的直接依賴應該用抽象替換允許自頂向下的

設計,而無需首先設計較低層。

1.3.2.-  其他重要的設計原則

  元件的設計必須高内聚:不要在元件裡增加不相關的功能。例如,避免把屬于領域模型的業務邏輯放到資料通路層。

功能内聚後,我們可以建立有多個元件的程式集,置于應用中相應的層中。是以這條原則與多層模式和單一職責原則

密切相關。

  把跨領域的代碼從應用的邏輯抽象出來:跨領域的代碼指的是水準方面的代碼,例如安全性,操作管理,日志,規範

等等。在程式中使用具體的實作,可能導緻在未來難以擴充和維護。面向切面程式設計(AOP)的原則和這部分有聯系。

  關注點分離:把應用程式劃分成不同部分并且最大限度地減少這些部分之間的重疊功能。關鍵點是最小化的互動的地方

以實作高内聚低耦合。然後,錯誤的分離功能點可能導緻高度的耦合和系統特性的複雜性。

  不要重複自己:想做的必須在系統的某一個部分。例如在一個應用設計中,一個特定的功能應該隻在一個元件裡實作;

這個功能不能在别的元件裡實作。

  最小化自頂向下設計(前期設計):隻設計需要的而不要過度設計,考慮靈活設計原則。

1.4.-  DDD架構的趨勢方向(領域驅動設計) Orientation  to DDD architecture trends 

  這篇架構架構的目的是提供了統一的基礎和一套具體應用類型“複雜業務應用”的使用者手冊。這種類型的應用的特點是

有一個相對長的生命周期并且可以承受相當數量的變化。在這些應用中持續的維護是非常重要的,包括更新/替代新的技術

和架構,例如換新版本的O/RM。目标是所有這些變化的對程式的影響最小。改變基礎設施層的技術不應影響進階别的層。

具體而言,“領域模型“層應該受到最小程度的影響。

  複雜應用中,業務規則的行為(領域邏輯)經常變化,是以保留對領域層修改,進行簡單且獨立的方式測試是非常重要的。

實作這個重要目标需要領域模型(邏輯和業務規則)和系統其他層(表現層,基礎設施層,資料持久化層等)之間的最小耦合。

  應用架構的趨勢是朝向實作層之間的耦合,尤其對于領域模型層來說。作為領域驅動設計的一部分,面向領域的多層架構

專注于這一目标。

  重要:

  領域驅動設計不僅僅是一種建議的架構;也是一種處理項目的方法,一種工作方法,基于領域專家(業務專家)的知識

識别通用語言的重要性,建立正确的模型等等。然而,這些方面不在本手冊裡;我們的範圍僅限于邏輯和技術架構内的典型的

架構模式和建議的.NET實作。請參閱面向領域設計相關的書例如(“Domain-Driven Design‟, by Eric Evans)和其他更詳細的

資訊關于如何應用面向領域設計到你的項目的生命周期裡

  您不應該采用面向領域的N-層架構的原因

  如果應用相對簡單,在應用的生命周期裡不會有基礎設施技術的改變,尤其是業務邏輯很少會變動,那麼你的解決方案就

可以不按照該手冊裡介紹的方法架構。相反,你應該考慮快速應用程式開發(RAD)。快速的實作在建構簡單的組建和層的解耦

不重要的适合非常有效,強調的是生産力和上市時間。通常情況下,這些種應用程式是資料驅動的應用程式,而不是領域驅動

設計。

 應該采用面向領域的N-層架構的原因

 當業務行為會随時間變化時,應該考慮使用面向領域的N-層架構。在這些情況下,領域模型對每次改變需要的工作會減少,

應用使用更耦合的方式會有更低的總擁有成本(Total  Cost  of  Ownership)。簡言之,在軟體的單獨區域封裝業務行為

會顯着降低修改應用的時間。這是因為修改隻在一個地方進行,并能很友善地進行隔離測試。能隔離領域模型的代碼很大的

減少了在應用其他地方進行修改(有可能帶來新的問題)的情形。如果你想減少并提高穩定周期和解決方案的調試,這是非常重要的。

  領域模型有效的情形

  

  業務規則的一些可操作說明可以用來确定是否實作領域模型。例如,在業務系統中,一個規則指明一個客戶不能有超過2000

元的拖欠款,這個規則應該屬于領域模型。實作這樣的規則通常涉及一個或多個實體,必須在不同用例的背景下進行評估。

  進而,一個領域模型會有很多的這樣的業務規則,包括一些可以取代其他規則的規則。例如,關于上述規則,如果該使用者是個

特殊的賬戶,拖欠款可以更高,等等。 

  簡言之,應用的業務規則和用例越重要,越應該适合領域模型的架構而不是簡單的

在一個面向資料的應用中定義的實體關系。

  最後,為了将通過将記憶體中的對象集(實體對象/圖)儲存到一個關系型資料庫,我們可以使用ORM類型來進行資料持久化,

例如Entity Framework 或者 NHibernate。然而,重要的是将這些具體的資料持久化技術(基礎設施技術)和應用的業務行為

很好地分離開。這需要一個應用松耦合方式的N層架構,我們将在後面看到。

1.5.-  分布式領域驅動設計(DDDD)

  四個D?是的,很顯然, DDDD在領域驅動設計上考慮到分布式系統的演變/拓展。 Eric  Evans,在他關于領域驅動設計的書裡

幾乎沒有提到分布式技術的相關主題,而書中由于主要關注的是領域。然而,在許多情況下。我們需要分布式系統和遠端服務。

  實際上,由于我們從一開始就考慮到分布式服務,本文裡的N層架構是基于分布式領域驅動設計的,同時我們用建議微軟的技術

進行實作。

  最終,分布式領域驅動設計使我們更接近分布式、高擴充性、甚至接近雲計算的情景。我們會在最後一章進行闡述。

2.- 面向領域的N層架構設計

  如上述,我們要弄清楚所講的是面向領域架構,而不是所有關于領域驅動設計的東西。如果要讨論面向領域架構,除了架構

還應該讨論設計過程,開發團隊的工作方式,通用語言等等。将會在此簡要地讨論這幾個方面。本手冊的目的是專注于領域

驅動設計的架構以及如果用微軟的技術實作。因為有很多不錯的書的讨論領域驅動設計,是以我們不打算在這裡詳細說明和解釋。

  本節提出了我們建議的面向領域的N層架構和層的總的定義。

2.1.- 表現層,應用層,領域層,基礎結構層  

  在最高和最抽象的級别,系統的邏輯架構視圖可以看做一組在内部有關聯的幾個層,類似于以下的圖(按照領域驅動設計的樣式)

領域驅動設計之代碼優先-架構描述 (翻譯)

在面向領域架構中,關鍵是要清楚界定和分離領域模型層和其餘的層。這是領域驅動設計的先決條件。“一切都必須圍繞領域,

領域模型層必須和基礎結構技術分離“。

  是以,複雜應用應該分層。應該在各個層内進行設計。設計必須内聚且和系統中的其他層明确界定邊界,應用标準的架構模式

使得依賴大多基于抽象而不應是層直接依賴别的層。領域模型的所有相關代碼都應集中在一層,和其他層的代碼分離出來。領域

不能有取得、儲存領域模型、管理應用程式的任務等等的代碼。領域必須專注于表達領域模型(資料和邏輯)。這使得領域模型

可以逐漸變得豐富和清晰來表現重要的業務知識,并在應用程式中實作的業務需求。

  把領域層從其餘各層分離開,可以讓每層的設計更加幹淨。分離的層是更容易維護,因為經常在不同的時候進行不同需求的修改。

例如,基礎結構層在技術更新時進行改造。另一方面,領域層隻會在業務邏輯變化的時候改變。

  此外,層的分離有助于分布式系統的部署,它允許不同的層部署在不同的伺服器上,以便最大限度地減少通信和提高性能(引用

M.  Fowler)。但是這種層的分布式部署取決于特定的應用需求(安全性,可伸縮性等等)

  層之間元件的松耦合是必不可少的。應用的每層都有一系列的元件構成。這些元件應該内聚,但是層之間應該松耦合來支援單元

測試,模拟和重用來減少維護的影響。主要層的設計實作松耦合想在後面詳細介紹。

2.2.- 面向領域N層架構

  這種架構的目的是按照領域驅動設計的模式,簡單清晰的建構多層的複雜業務應用程式。當用N層模式時,在應用中不同的内部層

和子層可以是不同的。

  當然,這個特殊的多層架構是可以根據每個項目或偏好來定制的。我們隻是提出一個建議的架構基礎,可以根據需要和需求進行

調整和修改。

  特别的,建議的“面向領域N層”應用的層圖如下:

領域驅動設計之代碼優先-架構描述 (翻譯)

- 表現層

   o  可視化元件子層(視圖)

   o  使用者界面邏輯子層(控制器及類似)

  - 分布式服務層(Web services) 

   o  瘦Web services門面模式

  - 應用層 Application  layer 

   o  應用服務(協調任務和用例)

   o  擴充卡(格式轉化等)

   o  工作流子層(可選)

   o  應用層基類(層超類型模式)

  -  領域模型層

   o  領域實體和聚合 

   o  工廠

   o  領域服務

   o  查詢規範(可選)

   o  倉儲接口/契約 

   o  領域基類(層超類型模式)

  -  資料持久化層

   o  倉儲實作

   o  基類(層超類型模式)

   o  資料邏輯模型和映射

   o  O/RM 技術基礎設施

   o  外部服務的服務代理

  - 架構的橫切元件

   o  安全性,營運管理,監控,郵件系統等方面。

  這裡簡單地說明這些層,會有一整章來分别介紹每一層。在這之前,有趣的是,從高層次的角度來看,層之間的互動的

很像我們這麼劃分的原因。

  領域驅動設計的主要前提和來源是Eric  Evans的“領域驅動設計- 應對軟體的複雜性“,書裡描述和解釋了建議的N層架構

高層次的圖:

領域驅動設計之代碼優先-架構描述 (翻譯)

值得注意的是,在某些情況下是直接通路其他層的。就是說,沒有任何理由層直接的關系必須是單向的,雖然這取決于每個應用

的情況。為了說明這種情況,下面我們展示了Eric Evans之前的一個圖。我們修改了這個圖,增加了一些細節,于低級别的子層

和元素有關。

領域驅動設計之代碼優先-架構描述 (翻譯)

首先,我們看到基礎結構層,該層為很多不同的環境(伺服器和用戶端環境)提供功能。基礎結構層包含和技術/基礎設施相關的

所有東西。還有一些基本概念,其中包含如資料持久化(倉儲等等),也有例如安全性,日志,監控等等的橫切主題。甚至還包含

具體的圖像方面的庫。由于軟體環境的巨大差異和資料通路的重要性,在本文的架構中,我們會把資料持久化層和基礎結構的其他

部分(通常是橫切基礎結構層)分開,這樣這部分可以被任何層已橫切的方式使用。

  另外一個我們遇到的情況是,不隻是通過一條單一路徑通路一些層。特别地,必要的時候我們可以直接通路應用,領域,橫切層。

例如,我們可以從表現層直接通路應用層,領域層或者橫切基礎結構層。然而,通路資料持久化層和層内的倉儲對象,通常建議通過

應用層的協調對象(服務)來進行,因為應用層的對象是協調大部分基礎設施對象的子產品。

  值得一提的是,這些層的實作和使用應該領靈活。将來也許圖中會有更多的組合箭頭。是以,不用在所有的應用有使用一樣的方法。

  此章的後面我們會簡單介紹每一層和子層。也會提出了一些關于如何定義和實作這些層的總的概念。(例如層間的松耦合,不同實體

層的部署等等)

  接下來,在下面的章節我們會詳細解釋每個進階别的層。

  表現層

  這層的作用是給使用者展示資訊和解釋行為。  表現層的元件實作使用者與應用互動的功能。

一般建議用MVC,MVP或者MVVM這樣的模式來分隔這些元件為子層。

o  可視元件子層(視圖): 裡面的元件提供終端使用者使用應用的能力。裡面的元件用可視化控件顯示資料

以及從使用者取得輸入資料。

o  控制器 : 從圖形界面分離出元件有助于使用者互動。這使得控件、頁面不包含處理流程和狀态管理邏輯的代碼,

可以讓我們脫離界面來重用代碼的邏輯和模式。對表現層的邏輯進行單元測試頁很有用。控制器通常基于MVC模式

及其衍生品。

分發服務層(Web服務)

  當應用作為服務提供者為遠端應用或者當表現層位于遠端端時(富用戶端,RIA,OBA應用等等),業務邏輯

通常通過分發服務層釋出。這層提供了提供了一種基于通信信道和資料資訊的遠端通路。需要注意的是,本層

應該越薄越好而且不應該包含業務邏輯。

應用層

  這層是建議的面向領域架構中的一部分。這層調用領域層和基礎結構層(資料持久化等等)來完成應用的用例。

  實際上, 應用層中不應有領域規則或者業務邏輯;應該執行程式的調用,而這是不用解釋給領域專家或者使用者

的。在應用層中,我們實作協調應用的“通道”,例如事務,執行機關操作,調用應用程式的任務。應用層中可以

實作其他的功能,比如應用優化,資料轉換等等,都可以稱為應用程式的協調。最終的操作,将會被委派到低層

對象。應用層中不能有表示内部業務邏輯的狀态,但可以有展示給使用者的表示執行程式任務的狀态。

  由于像為領域模型的一個門面,應用層有點類似于“業務外觀”模式。但是,應用層不僅是簡單的調用領域。包括

在這一層的功能有:

  -  協調資料持久層的倉儲對象的調用

  -  分組/合并更高層需要的實體資料,實作減少遠端調用的次數來增加效率。傳送的資料為資料傳輸對象(DTO),

  将實體和資料傳輸對象進行互相轉換的叫做DTO擴充卡。

  -  響應使用者界面的操作的操作,執行領域的運算,調用相應的資料通路運算。

  -  維護應用相關的狀态(不是領域對象的内部狀态)。

  -  協調領域和基礎結構層的操作。例如,執行一個銀行轉賬需要從倉儲擷取資料,然後用領域對象的轉賬業務邏輯

  ,最後可能給相關人發送郵件。

  -  應用服務:注意這個服務不是Web服務。首先,服務的概念在很多層裡都有:應用層,領域層甚至基礎設施層。

  服務的概念是一組類,操作若幹低層的類。是以,服務一般用來協調底層的對象。

     應用服務,就是一般來協調其他底層的服務(領域服務或橫切基礎結構層服務)。例如,應用層可以調用領域層

  來執行在記憶體中建立一個訂單對象。當領域層執行這樣的業務操作(多數改變的是記憶體中的對象),應用層會調用

  基礎設施層的倉儲來執行資料源的操作。

  -  業務工作流(可選):一些業務流程包括幾個步驟,這應該按照具體的規則來實作,而且通常比較耗時。這種

  業務流程應該由業務流程管理工具的工作流實作。

  應用層也可以通過Web服務層作為一個門面釋出,這樣就可以被遠端調用。

領域層

  領域層負責展示業務/領域概念,業務流程的狀态和領域規則的實作。應該包含展示業務流程的狀态。

  領域層是軟體的心髒。

  為此,領域曾的元件應該實作系統的領域核心功能,封裝所有相關的業務邏輯(領域驅動設計術語裡的領域邏輯)。

基本上,是一些用方法實作領域邏輯的類。按照面向領域的N層架構模式,領域層應該對資料持久化細節透明。

  通常我們可以在領域層裡定義下面的元素:

  領域實體:領域對象包含資料和邏輯,用來在層間傳輸實體資料。領域驅動設計的一個基本特征是,領域實體包含領域

邏輯。例如在銀行賬戶實體中,存款的操作應該在賬戶實體内部進行。也可以包括資料驗證,屬性計算,和其他實體的關系

等等。最後,這些類表達了現實世界中的實體。另一方面,應用内部的實體是在記憶體中的有着資料和邏輯的對象。如果隻用

實體來做資料傳輸,沒有相關的邏輯,我們會陷入最初由Martin Fowler描述的貧血領域模型的反模式,另外,推薦使用POCO

(Plain  Old  CLR  Objects)實體,一種不基于任何資料通路技術或架構的類。該設計(持久化透明)的最終目标是領域類不能有任何直接資料

通路技術的引用。

  由于領域類必須獨立于任何基礎結構技術,領域類必須放在領域層内。所有的情況下,實體是貫穿架構中最多層的對象。

  關于領域驅動設計的定義,并且根據 Eric Evans的“一個由标示符定義的對象叫做實體”。實體是領域模型的基本概念,

必須仔細辨認和設計。在一些應用中的辨別在别的應用可能不是。例如,位址在一些系統可能不是辨別,但在其他的系統,

例如電力公司,客戶的位址很重要,應該作為一個實體。

  聚合:聚合是有清晰邊界的實體和值類型對象的組合。将會在領域模型層的章節具體解釋聚合。

  工廠:當建立一個聚合很複雜時,用工廠來建立聚合就很有用。将會在領域模型層的章節具體解釋工廠。

  領域服務:在領域層,服務是一些組織執行領域邏輯的類。這些類一般不應該有領域相關的狀态(無狀态類)。

這些類用來協調領域實體的操作。典型的領域服務同時關聯幾個實體。但也可以有負責隻和一個根實體互動的服務。

關于倉儲,一般由應用層調用,尤其當執行事務或者使用工作單元模式(UoW)的時候。但有時需要根據領域邏輯

來從倉儲擷取資料,這種情況(一般是查詢),可以在領域服務中使用倉儲。

  倉儲契約:顯而易見的是,倉儲不在領域中實作,而是基礎結構層的一部分。然而,接口(契約)必須屬于領域。

契約表明了倉儲應該提供什麼來滿足領域,而不管倉儲内部是如何實作的。這些接口(契約)不應該知道使用的技術。

另一方面,實作這些接口的類會用某種技術來實作。是以重要的是倉儲接口(契約)必須在領域層定義。這是按照

Martin  Fowler 的分離接口模式,推薦的面向領域架構的模式。從邏輯上講,為了能夠遵守這條規則,領域實體和

值類型需要是POCO類型;即負責維護實體和資料的對象必須對資料通路技術透明。必須考慮到領域實體最終是倉儲

傳遞是參數“類型”。

  資料持久化基礎結構層

  這層提供持久化和通路資料的功能。資料可以是自己的系統或者外部系統的。是以,資料持久化層給進階的層

公開資料通路。這種公開應該是用過松耦合的方式。

  - 倉儲的實作:倉儲,通用的術語是”在一個組内表示某一特定類型的所有的對象“(Eric  Evans的定義)。

實踐的方面,一個倉儲通常是一個用某種技術完成持久化和資料通路操作的類。通過這樣做,我們把資料通路功能放在

一個地方,這樣可以更友善和直接的維護和配置應用。通常,我們為每個根實體建立一個倉儲。根實體有時候隻有一個

實體,有時候可以是一個複雜的聚合,包括很多實體,值類型。

    應該通過在領域層的接口通路倉儲,這樣可以在領域層來進行分離倉儲的單元測試,或者用另一種技術實作倉儲

而不影響領域層。

    倉儲的關鍵是使得開發人員可以集中注意力在領域邏輯上,通過倉儲契約來隐藏資料通路的實作方式。這個概念

叫做持久化透明,這意味着領域模型完全不知道資料的存儲和查詢方式。

    最後,需要區分資料通路對象和倉儲。主要的差別是資料通路對象在存儲上直接進行持久化和資料通路操作。

然而,倉儲先在記憶體中标記/儲存對象,以及要進行的操作,但是會在稍後才進行真正的執行。這就是在應用層

這些持久化/資料通路操作會在一個事件中一次完成。通常基于工作單元模式,這會在下面的章節詳細介紹。工作單元

模式可以提升應用的性能,也可以減少不一緻的可能性;在高擴充系統裡,減少由于事務引起的資料庫鎖數目。

  - 基本元件:大部分的資料通路任務需要共通的邏輯,可以抽出并在一個單獨的可重用的元件裡。這有助于簡化

資料通路元件,尤其是減少需要維護的代碼量。這些元件可以有基類或者工具類的方式實作,可以在不用的項目重用。

這個概念是一個非常有名的由 Martin Fowler定義的分層超類模式,主要說的是“如果把類似的類中的通用行為抽象到

基類裡,會減少很多重複的代碼”。使用這個模式純粹是為了友善但不要分散關于領域的注意。

    “分層超類模式”可以在任何類型的層中使用。

  -  資料模型/資料映射:這是有映射領域實體模型到資料庫表的ORM。按照選擇的ORM工具,映射可以是基礎代碼或者

可視化模式。

  -  代理服務:有時候業務元件需要使用外部/遠端服務提供的功能。在這些場景,需要實作一個管理通信并且映射資料

的元件。代理服務隔離特定的接口,這樣可以模拟外部的服務來進行單元測試,甚至用另一個服務替換而系統的核心部分

不受影響。

  橫切基礎結構層

  這層給其他各層提供了通用的技術能力。最後,這層用它的功能“堆積木”。應用中有很多任務是要在不同層裡實施的,

可以被各層使用的橫切面。最常見的橫切面有:安全性(身份驗證,授權和驗證),營運管理(政策,日志,跟蹤,監測,

等等)。這些方面會在下面的章節詳細介紹。

  - 橫切基礎結構服務:服務的概念也是關于該層的。這些服務負責組織和協調基礎結構動作,例如發送郵件,監控安全事件,

營運管理,日志,等等。這樣,這些服務負責組織所有的技術方面的事宜。

  - 橫切基礎結構對象:根據橫切基礎結構對象的類型,來實作需要的特定對象,不管是安全事件、跟蹤、監控等等

需要用指定的API。這些橫切基礎結構層覆寫很多方面,很多和服務品質(QoS)有關,實際上和具體的技術相關。更多

的詳情将在一章中讨論橫切面。

  服務是一個在不同層通用的概念

  由于服務中在DDD架構的不同層都出現,我們在下面的表中總結了DDD中服務的概念:

  表2:面向領域架構的服務

  我們已經看過了所有的層,它們都可以有服務。由于服務在不同地方都有它的意義,我們可以比較友善的來看

服務在DDD中的總體方面。

  首先,需要注意的是服務并不是為了進行遠端調用的Web服務。Web服務可以位于分發服務層,可能給遠端調用

或者應用、領域層使用較低層的服務。

  DDD服務的概念,最幹淨實用的設計中,包括了層内不屬于同一對象的操作(例如對多個實體的操作)。這種情況

下我們可以把這些操作組織為服務。

  這些操作自然是由對多個對象的活動組成。由于程式設計模型是面向對象的,我們也應該把操作組織成對象。這些對象

叫做服務。

  這麼做的動機是,如果把這些操作作為原來對象的一部分,會歪曲真實對象的定義。例如,實體在邏輯上應該是和

内部的機制例如驗證該實體等相關,但不應該把實體本身看做一個整體。例如,一個發動機實體執行有關發動機的

行為,而不應該和發動機是怎麼制造的相關。同樣地,實體類的邏輯不應該管理它的持久化和存儲。

  此外,服務是一個或一組作為接口提供的操作。服務不能封裝狀态(必須是無狀态的)。這并不意味着實作的類必須

靜态的;一般情況下回是一個執行個體類。服務是無狀态的意味着服務端程式可以使用任何該服務的執行個體,而不用管每個

對象的狀态。更重要的是,服務的執行可以使用甚至更改全局資訊。但是服務本身沒有狀态來控制它自己的行為,

不像實體。服務這個詞在服務模式裡描述的提供了:服務可以提供給調用它的用戶端的是,強調每一層與其它對象的關系。

  服務一般由操作命名,而不是對象名。是以,服務和用例關聯,而不是對象,即使有特定操作的抽象定義(例如,

“轉賬服務”和動作“從一個銀行賬戶轉錢到另一個”相關)。

  為了解釋這一點,怎樣在不同的層中區分服務,下面舉了一個簡單的銀行情景:

  應用層:應用服務”銀行服務‟

             -接收并轉化輸入資料(例如把DTO轉化為實體)。

    -提供領域層轉賬的資料,以便在領域層處理業務邏輯。

    -調用基礎結構層的持久化對象(倉儲庫)來儲存領域層的變化。

    -決定是否要用橫切層的服務發送通知。

    -最後,實作了所有“協調技術的管道”,使領域層隻負責清楚的表現邏輯。

  領域層:領域服務”銀行轉賬‟(動詞轉賬)

             -調用例如銀行賬戶的實體的方法。

    -提供了業務操作結構的确認。

  橫切層:橫切服務"發送通知"

              -協調郵箱發送或者其他類型的通知,調用基礎結構的元件。

  到目前為止,按照本章中所有的解釋,你可以推斷出按照商業應用開發中什麼是第一條準則:

  表3:DI設計原則

  原則 #:D1. 複雜應用的内部架構應該設計成為基于多層應用的架構并且面向領域。

o 規則 

- 通常,這條規則可以應用到有很多領域邏輯和長生命周期的複雜商業應用。

  何時使用面向領域的多層架構

- 應該被使用到複雜的,有很多變化的業務邏輯的,有着相對長生命周期需要後期維護的商業應用。

  何時不應使用面向領域的多層架構

- 在小型的完成後幾乎不會有改變的應用。這類的應用有一個相對短的生命周期,開發速度優先。

這類應用推薦使用快速開發技術。然後,實作更複雜耦合的元件時會有缺點,這将導緻在應用有相對

較差的品質。是以,技術的更新和未來的維護費用會随應用是否有大的改變而定。

  使用多層架構的優點

-  在一個組織中,不同的應用使用結構化,同質活類似的開發流程。

-  簡單的應用維護,由于不同類型的任務總是位在架構的同一地方。

-  改變應用的實體部署時更容易。

  使用多層架構的缺點

-  在非常小的應用中,增加了過多的複雜性。這種情況下可能是過度設計。但這種情況下是不太

可能在有一定複雜性的商業應用出現。

參考

Eric Evans: Book “Domain-Driven Design: Tackling Complexity in the Heart of 

Software” 

Martin Fowler: Definition of „Domain Model Pattern‟ and book “Patterns of 

Enterprise Application Architecture” 

Jimmy Nilson: Book “Applying Domain-Driven-Design and Patterns with examples in 

C# and .NET” 

SoC - Separation of Concerns  principle: 

http://en.wikipedia.org/wiki/Separation_of_concerns 

EDA - Event-Driven Architecture: SOA Through the Looking Glass – “The 

Architecture Journal” 

EDA - Using Events in Highly Distributed Architectures – “The Architecture Journal” 

  盡管這些層最初是為了覆寫多層應用架構的大部分,對于特定的應用,基礎架構對引進新的層和進行

定制式開放的。

  同樣地,完全實作建議的層不是強制的。例如,在某些情況Web服務層可能不需要實作,因為不需要

遠端通路,或者你可能不想實作某種模式,等等。

2.3.- 元件之間解耦

  需要注意的是應用的元件不應該隻在層間定義;我們還應該特别注意元件之間怎樣互動,那就是,它們

怎樣被使用,尤其是一些對象被另外的對象執行個體化。

  通常,應該在所有屬于不同層的對象之間解耦,,由于在應用中有一些層我們想以解耦的方式進行內建。

這是基礎結構層的大多數情況,例如資料持久化層,可能和特定的ORM方案結合,或者是一個特定的外部後端。

簡言之,為了在層中實作解耦,不應該直接執行個體化層中的對象(例如,不直接執行個體化倉儲對象或其他和特定

技術相關的基礎結構層的對象)。

  這點本質上是關于任何類型對象的解耦,不管他們是不是領域層裡的對象,或者表現層裡的元件可以模拟

Web服務的功能,或者在持久化層裡能夠模仿外部Web服務等等。在這所有的情況下,應該用解耦的方法操作

為了用最小的影響可以用模拟的實作替換真實的實作。在所有這些例子中,結構式非常重要的方法。

  最後,我們在應用中實作“使用最先進技術”的内部設計:“應用的體系結構都采用解耦的方式建構,讓我們

可以在任何地方和事件增加功能。不隻是在層之間使用解耦。”

  隻在層之間進行解耦可能不是最好的方法。例如在領域内部增加不同對象的集合(例如,一個用戶端包括

垂直子產品)解釋了上述。

  在該架構指南的示例應用中,我們選擇為應用中的層的大部分對象解耦。是以這種方法是完全可用的。

  解耦的技術基于依賴倒置原則,這闡明一種特殊的解耦方式,即将傳統的面向對象的依賴關系反轉。目标是

讓層獨立于具體實作的其它層,是以和實作的技術無關。

  依賴倒置原則的如下所述:

 A. 高層不應依賴于低層。二者都應該依賴于抽象(接口)。

 B. 抽象不應該依賴于細節,細節應該依賴于抽象(接口)。

  這條原則的目的是為了高層的元件于低層的元件解耦,以便重用高層的元件而使用不用的低層元件。例如,

重用領域層而使用不同的基礎結構層,但是實作在領域層定義的同樣的接口。

  契約/接口定義了低層元件的行為。這些接口應該在高層的程式集中。

  當低層元件實作接口,意味着低層元件依賴于高層的元件。是以,傳統的依賴關系被反轉,這就是為什麼

這叫做“依賴倒置”。

  有幾種技術和模式來實作依賴倒置,例如Plugin, Service Locator,依賴注入和控制反轉(IoC)。

  我們建議的實作元件解耦技術如下:

  - 控制反轉(IoC) 

  - 依賴注入(DI) 

  - 分布式服務接口(提供給遠端通路的層)

  正确使用這些技術,可以得到下面的好處:

  -  可以替換目前的層/子產品而不影響應用。例如,資料庫通路子產品可以被替換成通路外部系統或其他系統,

  隻要實作了相同的接口。為了增加一個新的子產品,我們不需要确定直接的引用或者編譯使用它的層。

  -  可以使用在測試時使用STUBS/MOLES和MOCKS:這是一個真實的“替換子產品”的場景。例如,用一個假的

  資料通路子產品替換一個真實的資料通路子產品。依賴注入甚至允許在運作過程中進行替換,而不用重編譯

  解決方案。

2.4.- 依賴注入和控制反轉

  控制反轉模式:這代表選擇一個類的具體實作由外部的元件或代碼決定。這個模式描述了一個支援“插件”

式的架構,對象可以搜尋需要或者依賴的執行個體。

  依賴注入模式:實際上是控制飯莊的特例。模式裡,對象/依賴提供給類,而不是類自己建立對象/依賴。

這個術語最早由 Martin Fowler提出。

  我們不應該顯式的執行個體化不同層間的依賴。可以使用一個基類或者接口(最好是接口)來實作,該接口

定義了一個共通的抽象來進行對象執行個體的注入。

  最開始,對象注入可以用對象工廠,工廠在初始化時建立了依賴的執行個體。然而,如果我們想在需要依賴執行個體

的時候得到它,需要引入“依賴注入容器”(DI Container)。依賴注入容器注入需要的每個對象的依賴關系。

需要的依賴關系可以用代碼或者XML來配置。

  通常,應用由外部架構提供依賴注入容器(例如Unity,MEF,Castle-Windsor, Spring.NET等等)。是以,是應用

中的依賴注入容器執行個體化類。

  開發人員将開發接口的實作類,用到容器來注入對象的執行個體。對象執行個體注入的技術有“接口注入”,“構造器注入”,

“屬性注入”和“方法調用注入”。

  當使用以來注入來進行對象間的解耦時,最終的設計會實作“依賴反轉原則”。

  一個有趣的在表現層使用依賴注入容器解耦的場景,為了在一個孤立的可配置的方式下執行模拟,stub/mole的

元件。例如,在MVC或MVVM的表現層,可能需要模拟Web服務來進行一個快速的單元測試。

  當然,最好的解耦是為大多數層中的對象使用依賴注入容器和依賴反轉。這将使我們可以在任何運作或者安裝的

的時候注入不同的實作行為。

  簡言之,依賴注入容器和依賴注入增加了項目的靈活性,了解能力和可維護性,最後會當項目推進時更少的改代碼。

表 4.- 依賴注入和對象的解耦

  單一職責原則指出,每個對象應該有一個唯一的職責。

  這個概念由Robert  C.  Martin引入。它規定改變的原因由于一個職責的改變,一個類必須有且隻有

一個理由來改變。

  這一原則在業界已被廣泛接受,有利于設計和開發單一職責的類。這是直接關系到依賴的數量,即對象

依賴的類。如果一個類有一個職責,它的方法一般隻有很少的依賴。如果一個類有很多依賴(假如有15個),

這表明這段代碼有着“壞氣味”。實際上,在構造函數中進行依賴注入,我們被迫在構造函數中聲明所有的對象依賴。

在該例子中,我們将清楚地看到這個類沒有遵照單一職責原則,因為一個類有單一職責不應該有這麼多的依賴。

是以,依賴注入可以引導我們實作良好的設計和實作,提供了一個解耦的環境。

表 5.- 控制反轉和依賴注入不僅有利于單元測試

  這是至關重要的。依賴注入和控制反轉不僅有助于單元測試和內建!說這就好像是說接口的主要目标是可以測試。

  依賴注入和控制反轉是關于解耦,使應用更靈活,有一個集中的地方可以維護。測試很重要,但不是使用依賴注入

或者控制反轉的首要原因。

  另一點需要澄清的是控制反轉和依賴注入不是一個東西。

表 6.- 依賴注入和控制反轉的差別

  請記住依賴注入和控制反轉是不同的。

  依賴注入确實可以幫助測試,但它的主要用處是它把應用帶向了單一職責原則和分離關注點原則。是以。依賴注入

是一個重點推薦的技術,是軟體設計和開發的最佳實踐。

  由于我們自己實作依賴注入可能非常麻煩,我們使用控制反轉容器來提供對象的依賴圖形管理的靈活性。

表 7.- 設計原則 Nº D2 

原則 #:D2。不同層之間對象的通信應該是解耦的,使用依賴注入和控制反轉的模式。

o 原則 

-  一般說來,這條原則應該在所有的多層架構的大中型應該使用。當然,應該被使用到對象的主要職責是邏輯執行

的時候。明顯的例子時服務和倉儲庫,等等。另一方面,對實體類自己這麼做沒有任何意義,應該他們是POCO實體類,

并沒有外部的依賴。

  什麼時候使用依賴注入和控制反轉。

- 在幾乎所有的大中型應用中都應該使用。在領域層、基礎結構層和表現層都是特别有用的。

  什麼時候不要使用依賴注入和控制反轉。

- 在解決方案級别,一般依賴注入和控制反轉不會用在快速應用開發商。這類應用不需要一個靈活的多層架構,

也沒可能引入解耦。這通常出現在小型的應用程式。

- 在對象級别,在沒有依賴的類中使用依賴注入和控制反轉沒有意義。

  使用依賴注入和控制反轉的優點

- 以最小的代價替換層/子產品。

- 易于使用STUBS/MOCKS/MOLES進行測試。

- 項目推進時,可以更少的動代碼

- 可維護性更高,更易了解項目

  使用依賴注入和控制反轉的缺點

- 如果不是每個開發人員都熟悉依賴注入和控制反轉,應用開始的階段會增加難度。然而一旦了解了之後,會給

應用帶來更好的靈活性,最終完成高品質的軟體。

參考

依賴注入:  

MSDN - http://msdn.microsoft.com/enus/library/cc707845.aspx 

控制反轉:  

MSDN - http://msdn.microsoft.com/en-us/library/cc707904.aspx 

依賴注入和控制反轉模式 (By Martin Fowler) –  

http://martinfowler.com/articles/injection.html 

2.5.- 子產品(代碼的分割群組織)

  領域模型往往在大型複雜的應用中大幅增加。模型将達到一個程度,很難把它作為一個“整體”讨論,可能很難

完全了解所有的關系和模型之間的互相作用。是以,需要将代碼組織到幾個子產品中(使用程式集/包/命名空間)。

  子產品的概念實際上從軟體開發依賴就被用到。如果我們細分不同的垂直子產品,很容易看清楚系統的全貌。一旦

了解了子產品之間的互動,容易更詳細的關注每個子產品。這是一個管理複雜性的有效方法。“分而治之”是對這最好的

定義。

  子產品用來組織概念和相關的任務的方法(通常不同的應用領域)。這使我們可以從外部的角度來減少複雜性。

  一般一個模型(領域模型)可以和一個或多個子產品相關。是以分離成一些子產品基本上是代碼分割。

  很明顯子產品間應該高内聚低耦合,不隻是把代碼分成子產品,更是概念上的分隔。

  不能把子產品和有界上下文混淆。有界上下文會在下面讨論。一個有界上下文包含了整個系統/子系統,甚至有資料庫。

另一方面,一個子產品通常和其他子產品公用模型。子產品是關于代碼和概念的分隔。

  像E.E說的,“如果一個模型像一本書在講一個故事,子產品就是章節”。選擇可以講述系統故事的,有相關概念的子產品,

并且給子產品按照領域專家的行業語言命名。了解更多領域驅動設計的語言,請閱讀Eric Evan的“領域驅動設計”。

  子產品的劃分的一個很好的例子是ERP(有可能是有界上下文的好例子,根據ERP實際的領域來定)。ERP一般通常劃分為

垂直的子產品,每個子產品負責特定的業務領域。ERP子產品的例子有工資,人力資源管理,計費,倉庫等。正如前面提到的,

每個ERP的垂直子產品根據它的大小和是否有它自己的模型,也可以是有界上下文,另一方面,如果所有的ERP公用一些

模型,這些方面就是子產品。

  另一個使用子產品的原因是代碼品質。代碼應該高内聚低耦合是行業公認的原則。雖然内聚始于類和方法的級别,也可以

在子產品的級别應用。是以,推薦把相關的類組織到子產品中實作最大的内聚。有幾種類型的内聚。最常用的兩種是“通信内聚”

和“功能内聚“。通信内聚是關于操作相同資料集合的子產品裡的部分。把它們聚集起來非常有意義,因為這些代碼有

比較強的聯系。另一方面,功能内聚是關于子產品中的全部都執行一個或一組定義的功能任務。這是内聚的最好的類型。

  這樣, 在設計時使用子產品是一個好的增加内聚減少耦合的方法。通常子產品會劃分出不同的功能區域。然而,正常情況下

不同子產品之間有一些通信;是以,應該為它們的通信定義接口。最好通過調用另一個子產品的接口來增加/聚集一組功能。

這樣也可以減少耦合。

  建議的架構計劃如下,考慮到應用中可能的不同子產品:

領域驅動設計之代碼優先-架構描述 (翻譯)

表 8.- 設計原則

原則 #:D3. 定義和設計應用子產品使共享相同模型的功能區域分隔開

原則

  通常,這條原則必須在大部分有一定功能區域的應用中使用。

什麼時候應該設計實作子產品

  應該在幾乎所有的業務應用中實作,這樣可以鑒别出不同的功能區域并實作子產品間的低耦合。

什麼時候不應該設計實作子產品

  應用中隻有一個非常内聚的功能區域,也很難把它分開到解耦的功能子產品。

使用子產品的優點

- 設計中使用子產品可以很好的增加内聚,減少耦合。

- 子產品間的松耦合可以減少複雜性,顯着增加應用的可維護性。

使用子產品的缺點

- 假設一個子產品的實體和其他子產品的實體有很多的實體關系,或許應該是一個單一子產品。

- 初期設計時需要額外定義子產品間的通信接口。然而,隻要子產品的定義和分離非常适合,将對項目非常有益。

 參考

Modules: DDD book – Eric Evans 

Microsoft - Composite  Client Application Library: http://msdn.microsoft.com/en-us/library/cc707819.aspx 

2.6.- 模型細分和工作的上下文

  在本節中我們會看到怎樣處理大型的模型或者叫複合模型;我們會展示把一個大的模型分成幾個具有明确的邊界

小模型來實作模型的内聚。這就是有界上下文的概念。

  注意:需要澄清的是有界上下文不是ORM工具的context或者sessions。這裡,有界上下文是一個更廣泛的概念,

我們讨論的上下文是關于獨立子系統和獨立開發小組的工作上下文,将在下文中看到。

2.7- 有界上下文

  在大型複雜的應用中,無論是它們的數量還是它們的關系模型都在快速的增加。在大的模型維護内聚是很複雜的。

兩個人對于同樣的概念有不同的的诠釋是很常見的,或者由于他們不知道已經實作了一個概念,而在另一個對象中

實作。為了解決這個問題,我們應該通過定義上下文來限制模型的大小。

  對整個系統隻有一個模型的想法是是誘人但不可行,因為維護這麼大模型的内聚幾乎是不可能的。事實上,我們

第一個應該問的問題是,當面對大型模型時,“我們需要完全整合系統所有的功能嗎?”。這個問題的答案是在90%的

情況是否定的。

  是以大型的項目/應用可以有多個模型,每個模型适用于不同的上下文中。但是如果我們有多個模型,問題就會

出現,是以我們需要顯式的定義系統中模型的邊界,這樣一個模型會盡可能保持統一。上下文可能成為應用的一個

重要部分并且和其他區域獨立開。

  在每個上下文中,不同的模型使用不同的術語,不用的概念和規則,甚至有時處理相似的資料。

  在大型系統中很難為所有的業務領域隻提出一個模型。将會有一個巨大的模型試圖滿足不同情景的所有業務領域。

這就像試圖取悅現實世界的每一個人,很難做到的。這就是我們需要上下文的一些模型來滿足大型系統的業務領域。

避免在大型系統中隻有一個模型處理很多垂直的業務領域。我們不能隻建立一個模型來為所有的使用者和相關人員服務。

“一個模型來統治他們"不是”一個戒指來統治他們“。

  任何系統中的元素隻有在上下文中定義才有意義。我們将專注于維護上下文的内聚,處理上下文的關系。上下文是

模型的分隔,旨在維護内聚,而不僅僅是簡單的功能分隔。定義上下文的政策可以有多個,例如按照團隊劃分,按照

系統的高層功能劃分等等。例如,一個項目中需要我們于一個已存在的系統并聯,顯然原有的系統有它的上下文,

我們不想讓新系統也處于一樣的上下文,因為這到影響新系統的設計。

  下面的圖展示了一些有界上下文,可能都是從頭開始建立相同的應用。可以看到通常每個有界上下文都有一個單獨

的模型,模型和其他有界上下文公用資料源。

領域驅動設計之代碼優先-架構描述 (翻譯)

實際上在大型系統中,根據需要每個有界上下文最好都都不同的架構。例如,下面可能是一個有幾個有界上下文的

應用,每個有界上下文的架構和實作都不一樣。這樣做可以讓每個應用都用最合适的方法構造。

領域驅動設計之代碼優先-架構描述 (翻譯)

注意:我們在最後一章中介紹了CQRS(指令查詢的責任分離)架構(高擴充性雲計算應用)。 雖然CQRS不必在雲端

實作,但是由于其高擴充性和雲很有關系。但這是另一個主題。

  最重要的一點是根據不同應用的需要,使用不同的方法。例如,在示例中,如果任務是顯示一些資訊,和其他的

有界上下文沒有關系,為什麼要使用一個複雜的多層或者CQRS架構呢?。

  盡管如此,在系統裡實作分開的上下文有一個缺點:失去了統一的視圖,當兩個上下文應該通信實作功能時容易

混淆。是以,如果我們需要連接配接有界上下文,需要建立一個上下文圖,這樣會清楚的标明系統的不同上下文和它們

間的關系。這樣就實作了内聚,上下文提供的内聚優勢,通過明确建立上下文之間的關系保持了系統的全局視圖的。

  在不同的有界上下文的整合一定會涉及一些模型間的轉化和資料轉換。

  同樣地,有界上下文的通信應該是異步的。在這裡使用輕量的服務總線可能是有用的。

表 9.- 設計原則

原則 #: D4. 

  大型系統中有多個模型時定義有界上下文

原則

-  在模型使用時顯示的定義上下文

-  在團隊組織,代碼和資料庫模式時顯示的設定界限

-  嚴格把模型限制在邊界内

參考

BOUNDED  CONTEXTS:  

DDD book – Eric Evans 

COMPOSITE  APPS and BOUNDED CONTEXTS: 

Udi Dahan session „Domain Models and Composite Applications‟ 

(http://skillsm atter.com /podcast/design-architecture/talk-from-udi-dahan) 

2.7.1.- 表現層,複合應用程式和有界上下文

  當不同的開發團隊開發不同的有界上下文時,在使用者界面層出現了一個問題。這個例子中,一般隻有一個

表現層,一個團隊對表現層的修改會影響别的團隊。

  結果是有界上下文和複合應用的概念緊密相關,不同的團隊獨立開發同一應用的有界上下文和模型。然而,

最終都必須內建到用于界面中。為了讓這種內建較少出問題,我們推薦使用”複合應用程式的表示層架構“。

  那就是,為每個可視的區域(慘淡,内容區域,加載視覺子產品,例如使用微軟MEF and  PRISM)定義接口,

這樣內建會高度自動化而不費力。

參考

Microsoft - Composite  Client  Application  Library:  http://msdn.microsoft.com/en-us/library/cc707819.aspx 

2.8.- 上下文間的關系

  上下文間的關系視它們間的通信而定。例如,我們也許不能在一個上下文中執行更改,例如在一個離線的系統

中,或者我們的系統需要其他的系統支援才能正常工作。在下面,我們會看到有界上下文之間整合的可能性,但

是要了解不應該強制有這些關系除非是正常出現的。

2.8.1.- 共享核心

  當我們有兩個或多個上下文,并且開發的團隊可以順暢的溝通時,建立一個共享的上下文都頻繁使用的責任對象

是不錯的。這些對象成為上下文的共享核心。修改共享核心的對象時,需要得到所有開發團隊的認可。推薦共同

建立一套對每個共享核心對象的單元測試。促進不同團隊之間的溝通是關鍵,是以,一個很好的做法是讓每個團隊

的成員到其他團隊中,這樣在一個上下文中積累的知識被輸送到其餘的上下文中。

2.8.2.- 消費者/提供者  Customer/Supplier 

  很容易意識到開發的系統依賴于别的系統。例如,一個分析系統或一個決策系統。在這類系統裡通常有兩個

上下文,一個是我們的系統使用依賴系統的上下文,依賴系統在另一個上下文中。

  兩個上下文的依賴是單向的,從“消費者”上下文到”提供者“上下文,或者從依賴系統到被依賴的系統,

  這類關系消費者會被提供者的功能限制。同時,提供者的上下文由于害怕引起消費者上下文的Bug而很少改變。

不同上下文的開發團隊的溝通是解決這類問題的方法。消費者上下文的開發團隊應該以消費者的身份參加提供者

的計劃會議來決定提供者用例的優先級。另外,應共同為提供者系統建立一組驗收試驗,這樣可以确認消費者

期望的接口,消費者上下文可以不用擔心改變期望的接口而進行修改。

2.8.3.- 遵奉者

  消費者/提供者的關系需要不同上下文團隊的合作。這種情況往往是比較理想的,往往提供者上下文有自己的

優先級,而不是按照消費者的需要安排。這類情形中,我們的上下文依賴于别的我們不能控制的上下文,也沒有

緊密的關系,可以使用遵奉者辦法。這涉及到我們的模型來适應其他上下文。這限制了我們的模型完成額外的

任務,限制了我們模型的形态。然而,添加其他模型已基類的知識不是一個不靠譜的想法。決定是否遵從遵奉者

很大程度上取決于其他上下文模型的品質。如果它不适合,應遵循更具防禦性的方法,例如防護層或其他的方法。

2.8.4.- 防護層

  這可能是對于有界上下文內建的最重要的模式,尤其是處理傳統的有界上下文內建。到目前為止我們看到的關系

假定兩個團隊直接有一個好的溝通,也有一個可被采用的有良好設計的上下文模型。但如果一個上下文的設計不佳,

我們不希望影響我們的上下文。對于這種情形,我們可以實作防護層,是一個上下文間執行轉換的中間層。 一般來說

,這種通信由我們發起,雖然不是強制性的。

  防護層有三種元件構成:擴充卡,轉換器和門面。首先,門面是用來簡化和其他上下文的通信,它暴露了我們的

上下文使用到的功能。需要明白門面應該的其他上下文定義;否則會混淆轉化器和其他系統的通路。門面之後,

擴充卡用來使其他上下的接口适配我們的上下文。最後,我們使用轉化器來映射我們的上下文元素和别的上下文。

2.8.5.- 其他的方法

  內建被高估,花費的成本往往是不值得。是以,兩組沒有連接配接的功能可以在不同的上下文中開發而不必互動。如果

我們有功能需要兩個上下文,可以在更高的級别來執行這個操作。

2.8.6.-  開放式主機 

  當我們開發一個系統,決定把它分成有界上下文時,一個常見的做法是在上下文間建立一個中間的轉換層。

當上下文的數目很多時,建立中間轉換層會花費相當大的額外工作量。當我們建立一個上下文時,它通常是

高内聚的,提供的功能可以看成是一組服務(不是Web服務)。

  這種情況下,最好建立一組服務來定義通用的通信協定讓别的上下文使用該上下文的功能。這項服務應保持

版本之間的相容性,但可以逐漸增加所提供的功能。暴露的功能應該是概要的,如果其他的上下文需要具體的

功能,那應該在一個分開的轉換層建立,這樣我們的上下文協定就不會被破壞。

2.9.- 用.net實作有界上下文

   正如我們在本節開始說過的,有界上下文是用來維護較大模型的内聚。為此,一個有界上下文可以對外部

系統展示系統功能的一部分,或者展示子系統功能的元件。實作有界上下文沒有通用的規則,但是我們會提出

幾個重要的方面并舉幾個典型的例子。

  在架構中,我們把模型和功能分成大型的區域(有界上下文)。每個區域或有界上下文都被配置設定給了不同的

開發團隊,它提供一組可以看作是服務的高内聚的功能。最合乎邏輯的事情是當我們處理一些業務領域時使用

不同方法的關系。每個子產品反過來會變成一個“open host”,以服務的方式提供一組功能。是以,任何涉及到

幾個子產品的功能都會在更高的層實作。每個子產品負責内部的對象模型,管理内部的持久化。當使用Entity Framework

時,我們在模型和有界上下文間是1對1的對應。

  我們為每一個有界上下文分成更小的部分(子產品)時都會足夠複雜。然而,這些子產品更多的和一個公用的模型相關。

這種情況,子產品更多的是一個組織單元(代碼分隔)而不是一個功能性的。有界上下文中不同的子產品共享相同的實體

架構模型,但是關鍵對象的修改需要兩個團隊的同意。

  最後,我們要找到系統中關于外部系統,遺留系統或第三方服務的方面。這些顯然是不同的有界上下文。這裡,

辦法可以是接受外部系統模型,采用“遵奉者”的方法,或者通過一個防護層保護我們的領域。決定是否按照遵奉者

或者選擇一個防護層根據其他上下文的模型品質和上下文間轉換的成本。

2.9.1.-  使用把Entity Framework模型分開 

  一個有效的分隔EF模型的方法是找到最互連的模型,通過移除模型或所有的關聯隻留外鍵。通常最互聯的實體是

那些對模型貢獻較少語義的,可能是一個橫切的模型,例如使用者,通過關聯到其他的模型的最後修改屬性。

  實體間并不總需要有關聯,我們将看到為什麼通過分析詳細的關系。什麼使得實體間需要有關系?通常一個

實體使用了另一個實體的功能。例如,考慮銀行賬戶和客戶的實體,客戶的資産通過計算該客戶所有賬戶的餘額來

得到。

  通常,兩個實體間的關系可以用其中一個實體倉儲的查詢來代替。這個查詢表示了該關系。在另一個實體的方法

裡,可以增加額外的參數來包含查詢結果的資訊,可以用來替代關系。

  兩個實體之間的互動在服務級别實作,因為這類互動并不常見,邏輯并不複雜。如果需要修改一個關聯

(增加或删除元素),我們在實體中查詢的方法中傳回布爾值來判斷是否需要實作,而不是修改方法來适應删除的

關聯。繼續賬戶和客戶的例子,讓我們假設我們想計算給客戶付的利息,這根據使用者特定而定。該服務會根據客戶

的資格把利息打到新的賬戶當利息超過一定數額時。該例中,我們将有下面接口的服務:

public interface IInterestRatingService 
 {  
   void RateInterests(intclientId); 
 } 
 
 
public class InterestRatingService : IInterestRatingService 
{   
  public InterestRatingService(IClientService clients, 
  IBankAccountService accounts) 
 { 
   … 
  }  

  public void RateInterests(intclientId) 
  { 
     Client client = _clients.GetById(clientId); 
     IEnumerable<BankAccount>clientAccounts = accounts.GetByClientId(clientId); 
     double interests = 0;  
     foreach(var account in clientAccounts) 
     { 
       interests += account.calculateRate(client); 
     }
     if(client.ShouldPlaceInterestsInaNewAccount(interests)) 
     { 
	     BankAccountnewAccount = new Account(interests); 
	     accounts.Add(newAccount); 
	  } 
	  else 
	  { 
	     clientAccounts.First().Charge(interests); 
	  } 
   } 
} 
           

2.9.2.- 有界上下文和程式集的關系

  有界上下文不一定需要建立具體的程式集。然而,根據上下文圖的關系,一些有界上下文會在同一程式集,和其他

的有界上下文分開。通常,由于每個有界上下文是和其他的是分開的,典型的做法是分散在不同的程式集中。

2.10.- 多層架構的映射技術

  在分析如何詳細定義Visual Studio的結構之前,最好看一下提到的高層視圖和使用的技術。

  這是一個簡單的架構圖,裡面隻有一個有界上下文的一些典型的層,并不考慮詳細的應用業務領域。這僅僅是一個

說明性的架構圖來看到模式和技術在具體哪裡。

領域驅動設計之代碼優先-架構描述 (翻譯)

 在下面的章節我們會詳細看到如何用表上技術實作架構的不同模式。

2.11.- 在 Visual Studio 2010 中實作多層架構

  為了實作多層架構(按照領域驅動設計多層架構的風格),需要做以下的步驟:

1.-  Visual  Studio解決方案應該清楚的呈現每一層和層内的實作。

2.-  每層都應該正确的設計并包括設計模式和該層使用的技術。 

3.-  項目中會有很多可重用的代碼。這是迷你架構或者叫seedwork。最終,會變成在其他項目中重用的代碼,

可能被一些有界上下文共享,也包括領域、應用、資料持久化層的基類。Seedwork這個詞最初有Martin Fowler

提出:http://www.martinfowler.com/bliki/Seedwork.html 

2.12.-  示例應用“面向領域多層.net 4.0 應用”

  大多數的代碼例子和解決方案結構都在示例應用中。我們強烈推薦下載下傳這份源代碼,按照該書檢視它。

  示例應用在CODEPLEX上釋出,有開源的許可:http://microsoftnlayerapp.codeplex.com

2.13.- Visual Studio 解決方案設計

  一旦建立了VS解決方案,我們就開始建立邏輯檔案夾架構來配置設定不同的項目。大多數情況下我們為每層

或子層建立一個項目(DLL),以提供更高的靈活性,更容易進行解耦。然而,這會産生一個相當大數目的

項目,是以需要由VS裡邏輯檔案夾來對它們進行排序/分級。

  最初的層次結構可能和下面的類似:

領域驅動設計之代碼優先-架構描述 (翻譯)

從最上面開始,第一個檔案夾("0 –Modeling & Design‟)裡面包括了不同的架構圖和設計圖,例如架構圖,

内部設計的UML圖。這些圖用來展示我們的實作。

  檔案夾的數字是為了讓他們以想要的順序出現在VS解決方案中。

  下一個檔案夾,“1 Layers‟,包括了多層架構的層,如上面的層次結構圖中所示。

表現層

  第一層表現層,包括了不同種類的表現項目例如RIA (Silverlight), RIA-HTML5,傳統的Web(ASP.NET), Windows Phone 

等等。這些用戶端隻是展示了可能的種類,很可能真實的應用隻有一個,

領域驅動設計之代碼優先-架構描述 (翻譯)

 随後,在應用伺服器會有元件層(我們指的是部署)。總之,會有面向領域的多層架構的主要的層,每個

子層都有不同的項目:

領域驅動設計之代碼優先-架構描述 (翻譯)

這些檔案夾中,我們會按照每層的典型元素添加項目。這也和要實作的模式有關(在該指南後續邏輯和

實作章節介紹)。考慮劃分每個子系統邊界和職責的有界上下文的概念。如果隻有一個有界上下文,前面的

層次結構是有用的。但如果有幾個有界上下文,最好按照下面的層次結構:

領域驅動設計之代碼優先-架構描述 (翻譯)

  Seedwork類似于可在項目,有界上下文等中重用的小型架構,但它不具有足夠的分量開發團隊稱之為架構。

在我們的示例應用中,所有包含“Seedwork”詞的項目都是可重用的程式集。就像之前提到的,Seedwork是由

Martin Fowler 提出的: http://www.martinfowler.com/bliki/Seedwork.html  

  每個有界上下文可能有相似的層,也可能沒有,這樣根據具體要求來定。

分發服務層(WCF 服務) 

  這層是為了實作應用伺服器的遠端通路的WCF服務。這層是可選的,因為在一些情況下是直接通路應用層和

領域層的。

  如果我用使用分發服務進行遠端通路,結構可能類似于下面:

領域驅動設計之代碼優先-架構描述 (翻譯)

 我們需要一個存放WCF服務的項目,WCF程序運作在其中。這個項目/程序可以是IIS的網站,Windows服務或其他

類型的程序。但是Web服務的功能是在每個子產品暴露的服務中。我們可以為每個子產品的功能建立一個WCF服務類。

在我們的例子中,有一個子產品叫"ERPModule‟,另一個叫“„BankingModule‟,當然這些代碼的分割取決于你的設計。

  另外,該層内需要一個單元測試的項目。

  對于一個WCF服務,推薦在IIS站點上部署,最好是在有AppFabric的Windows伺服器的IIS,這樣可以有AppFabric

提供的監控和測試能力。

應用層

  在本指南的邏輯架構部門,應用層不應該包含領域/業務邏輯。應用層應該協調應用方面的技術任務。這裡我們

實作了應用“管道”的協調,例如事務協調,工作單元,倉儲庫和領域對象的調用。

領域驅動設計之代碼優先-架構描述 (翻譯)

  有邏輯類的層都會有一個單元測試的項目。

領域層

  這層從業務/領域的角度看是最重要的,由于在該層實作所有的領域邏輯、領域實體類等。該層的内部

有幾個子層或元件。建議控制層内項目的數目。

領域驅動設計之代碼優先-架構描述 (翻譯)

我們可以重用由基類和其他可重用代碼的“Seedwork‟項目,這樣所有領域功能的每個有界上下文都可以使用。

  對每個應用的功能性有界上下文(本例中,叫"MainBoundedContext”),我們會實作完整的子系統邏輯

(聚合,POCO實體,服務,查詢和倉儲契約)。

  這是在我們示例應用中領域層一個有界上下文的内容:  

領域驅動設計之代碼優先-架構描述 (翻譯)

  有邏輯類的層都會有一個單元測試的項目。

  領域層會在本指南中用一整個章節來介紹概念和具體實作。

資料持久化層

  該層最重要的特征是倉儲庫的工作單元模式來進行資料通路和持久化。在該子產品中我們會實作所有和映射

領域實體和資料庫表,使用Entity Framework的API。

領域驅動設計之代碼優先-架構描述 (翻譯)

 本層中最重要的元素如下:

- 工作單元/上下文 (UoW):  對每個有界上下文都實作一個抽象的Entity Framework上下文,這樣我們建立了

一個清晰的契約聲明了我們的工作單元模式,并且可以通過替換上下文來模拟來進行孤立的單元測試。

-  倉儲庫:和工作單元對象共同負責資料持久化邏輯的類

  這兒也有子產品的概念。子產品隻是和更好的組織代碼分隔,共享相同的上下文和模型的代碼職責有關系。

  我們必須有一個測試整個項目的項目。

  項目前以“Seedwork‟為字首的是用來實作基類和擴充的,讓不同的有界上下文甚至不同的應用重用。

  資料持久化層也會在該指南的一整章來介紹概念和具體實作。

2.14.- VS.2010中的應用體系結構圖層

  為了更好地了解架構設計,用一張VS2010中的層圖來檢視多層架構。另外,這使我們可以讓我們将層映射

到它們實際的命名空間。是以,這使該架構的驗證針對實際的源代碼,是以層間的通路/依賴之間是禁止的,

而是代碼的行為。可以在工作流協作的引擎(TFS)中運作全局的編譯進行驗證,全局的測試或最後最後全局的

架構檢查。

  這是我們示例應用的多層架構圖:

領域驅動設計之代碼優先-架構描述 (翻譯)

  我們将在下面的章節介紹這些層的各個層,子層的邏輯和實作。這裡,我們提出一些全局的維面。

  我們可以看到在架構圖中,整個架構的核心層是領域層。在依賴級别也是非常重要的。大部分依賴在

領域層結束。領域層,對資料通路是持久化透明的。領域層對其他層隻有最少的依賴,如果有依賴都是通過

控制反轉容器的抽象(接口)。這就是為什麼依賴不是一個“箭頭”。

  另一方面,分發服務層是一個可選的。如果表現層在用戶端運作(Silverlight, HTML5/JScript, WPF,

WinForms 或 OBA),分發服務層就是必須的。然而,在Web用戶端(ASP.NET 或 ASP.NET MVC)的情況下,

表現層和業務元件是放在同意實體伺服器上。這種情況,不用使用WCF服務,因為這會影響應用的性能,

增加不必要的延遲和序列化。

  對于應用層,一般會是我們的“門面”層,應用服務協調關于領域層,資料持久化層和其他元件的調用。

2.15.- 使用UNITY實作依賴注入和控制反轉

  在本節中,我們會介紹實作層間元件解耦的技術。本節的目标是使用微軟的模式和方法Unity,實作依賴注入

和控制反轉。依賴注入和控制反轉也可以用其他的工具實作,例如:

表 9.- 控制反轉容器的實作

 架構   制訂人  資訊

Unity 

http://msdn.microsoft.com/en-us/library/dd203101.aspx  

http://unity.codeplex.com/  

微軟模式與實踐

這是目前實作依賴注入和控制反轉最完整的輕量級微軟架構。是有着微軟釋出許可的開源項目。

Castle 項目 (Castle Windsor) 

http://www.castleproject.org/  

CastleStronghold   

Castle 是一個開源項目。這是依賴注入和控制反轉最好的架構之一。

MEF  (微軟可擴充性架構) 

http://code.msdn.microsoft.com/mef  

http://www.codeplex.com/MEF  

微軟(.NET 4.0的一部分) 

目前是為了應用和工具自動擴充的架構,并不很關注使用依賴注入和控制反轉實作架構層間的解耦。

未來由于該架構的發展,可能替代Unity。

Spring.NET 

http://www.springframework.net/  

SpringSource

Spring.NET 是一個開源項目。這是最好的面向方面程式設計(AOP)的架構之一,也提供控制反轉容器的能力。

StructureMap 

http://structuremap.sourceforge.net/Default.htm  

.NET 社群的一些開發者

開源項目

Autofac 

http://code.google.com/p/autofac/  

Several developers of the 

.NET community  

開源項目

LinFu 

http://code.google.com/p/linfu/downloads/list  

http://www.codeproject.com/KB/cs/LinFuPart1.aspx 

.NET 社群的一些開發者

開源項目。提供控制反轉容器,面向方面程式設計(AOP)和其他能力。

  我們的示例應用選擇了UNITY,因為這是微軟提供的最完整的依賴注入和控制反轉能力的架構。當然,

在商業架構架構中,可以使用任何控制反轉容器的架構。

2.15.1.-  介紹Unity 

  該應用程式塊叫做Unity,是一個可擴充的輕量級依賴注入容器。支援構造器注入,屬性注入,方法調用

注入和嵌套容器。

  Unity是一個注冊類型(類,接口)的容器,也是在這種類型之間的映射(類似一個接口和實作該接口類的關系)。

Unity容器也可以按照請求執行個體化具體的類型。

  Unity可以從微軟的站點免費下載下傳,Unity也包含在Enterprise Library 4.0/5.0和PRISM (複合應用架構)中,擴充

了Unity的功能。

  通常在容器中記錄類型和映射,制定接口,基類,特定類型的對象間的依賴關系。我們可以用代碼來定義這些

記錄和映射,也可以使用XML配置檔案。同樣地,依賴注入可以通過在類的标志中指明主要注入的屬性和方法,

也可以使用構造函數的參數來指明需要自動注入的對象。甚至可以使用容器的擴充來支援“EventBroker”,它實作

了基于特性的釋出/訂閱機制。也可以建立自己的容器擴充。

   Unity提供了應用開發的這些優點:

-  支援需要的抽象;可以讓開發人員在運作時或安裝時指定依賴,簡化了橫切管理的方面,例如用模拟執行

單元測試。

-  簡化了對象的建立,尤其是那些有複雜分層結構和依賴的對象,最終簡化了應用的代碼。 

-  通過把元件的配置移到控制反轉容器中增加了靈活性。

-  它提供了服務定位能力;可以使用戶端可以儲存/緩存容器。這在ASP.NET Web應用中特别有用,可以把

容器存在session或ASP.NET應用中。

2.15.2.- 使用Unity的場景

   Unity可以解決基于元件應用的開發難題。現代企業的應用包含的業務對象群組件執行在應用中的一般

或特定的任務;此外,也會有一些負責應用中水準方面的元件,例如跟蹤,日志,驗證,鑒權,緩存和異常管理。

成功建構這些多層應用的關鍵是實作松耦合的設計。松耦合的應用靈活性更好,容易維護,更重要的是在開發

階段容易測試(單元測試)。可以模拟有較強依賴的對象。最終,可以對模拟或者真實的對象分别測試。依賴注入

是建構松耦合應用的重要技術。它提供了管理對象間依賴的方法。例如,一個處理客戶資訊的對象可能依賴于

通路資料庫的對象,驗證資訊,驗證使用者是否授權。依賴注入可以使客戶類執行個體化并執行這些它依賴對象,尤其當

是依賴是抽象時。

2.15.3.- 主要的模式

  下面的設計模式定義了簡化流程的架構和開發方法:

  控制反轉(IoC)。 該通用模式描述了一種支援“插件式”架構,其中對象可以搜尋需要的對象執行個體。

  依賴注入模式(DI)。其實這是控制反轉的一個特例。這是一種基于改變類的行為但不改變類的内部代碼的技術。

對象執行個體注入技術有“接口注入”,“構造器注入”,“屬性注入”和“方法調用注入”。

  截取模式(Interception pattern)。該模式引入了另一種間接的級别。在用戶端和真實對象中間加一個對象

(代理)。用戶端的行為像是直接和真實對象互動,但是代理攔截并處理真實對象和其他對象的執行。

2.15.4.- 主要的方法

  Unity在容器中暴露了兩個注冊類型和映射的方法:

  RegisterType():該方法在容器中注冊了一個類型。在合适的時候,建立具體類型的示例。這可能在依賴注入

通過類屬性初始化或解析的方法調用時發生。對象的生命周期由方法的一個參數決定。不過不傳該參數,類型

會被标記為透明的,意味着容器每次解析時都建立一個新的執行個體。

  RegisterInstance(): 該方法在容器中注冊了一個明确生命周期的指定類型的執行個體。容器在該生命周期傳回

執行個體。不過不傳生命周期,示例的生命周期由容器控制。

2.15.5.- 在Unity容器中注冊類型

  作為注冊類型和解析方法的使用,下面的代碼中,标記了ICustomerAppService接口,指定容器傳回

CustomerAppService類的執行個體。

C#  
//Register types in UNITY container  
IUnityContainer container = new UnityContainer();  
container.RegisterType<ICustomerAppService,   
                       CustomerAppService>(); 
... 
... 
//Resolution of type from interface 
ICustomerAppService customerSrv =    
                 container.Resolve<ICustomerAppService>();  
           

  在應用的最終版本,注冊和映射類型可能是用XML配置檔案,而不是C#代碼。這更靈活也可以在不用的

實施下改變配置。然而上面代碼展示的,在開發時間可能用寫死更友善,這樣可以在編譯時間發現

輸入錯誤而不是運作時。

  上面的代碼中,應用中會一直存在的代碼是容器中解析類的那行,也就是調用Resolve()方法。

  重要:如果你真想做好一個依賴注入,容器調用Resolve()應該在整個應用的一個地方進行。這個地方必須

是應用的入口點。可以放在WCF服務的初始化中。不能在應用中随便放container.Resolve()方法。那會是

服務定位器模式,多數情況下是反模式。

2.15.6.- 構造函數的依賴注入

  為了了解構造器的注入考慮這樣的場景,我們使用Unity容器的解析方法,該類的構造函數有一個或多個

參數(依賴其他類)。是以,Unity容器根據構造函數會自動建立需要的對象。

  舉例來說,不使用依賴注入或Unity的代碼,我們希望修改代碼,使用Unity以實作松耦合。這段代碼使用

叫做CustomerAppService的業務類:

… 
{      
  CustomerAppService custService = new CustomerAppService(); 
  custService.SaveData(“0001”, “Microsoft”, “Madrid”);  
}
           

  值得考慮的是,這段代碼可能是請求伺服器的開始。這可能是WCF服務的方法。下面我們實作不用依賴注入

的服務類(CustomerAppService),使用了資料通路層的CustomerRepository類。

C#  
 
public class CustomerAppService 
{  
     
    private ICustomerRepository _custRepository; 
     
    public CustomerAppService() 
    {          
        _custRepository = new CustomerRepository(); 
    } 
 
    public SaveData()  
    {          
        _custRepository.SaveData("0001", "Microsoft", "Madrid"); 
    }  
} 
           

  目前為止,上面的代碼和控制反轉和依賴注入無關,也沒有使用Unity的依賴注入。也就是說,這是傳統

的面向對象代碼。現在我們來修改CustomerAppService類,使得建立該類的依賴是由Unity容器自動執行個體化的。

就是說在構造函數中增加依賴注入。

C#  
 
public class CustomerAppService 
{  
//Members 
private ICustomerRepository _custRepository; 
 
//Constructor 
public CustomerAppService (ICustomerRepository customerRepository)  
{  
    _custRepository = customerRepository; 
}  
public SaveData()  
{          
    _custRepository.SaveData(“0001”, “Microsoft”, “Madrid”); 
} 
} 
           

  需要記住的是我麼不會顯示的調用CustomerRepository類(沒有 new())。Unity做為一個容器會

自動建立CustomerRepository對象,作為構造函數的參數傳入對象。這就是構造函數的依賴注入。

  在運作時,初始化伺服器的第一個類的入口點使用Unity容器的Resolve(解析)方法,方法由Unity

架構初始化CustomerAppService 和 CustomerRepository的執行個體。

  下面的代碼中,我們會實作使用應用服務的最高層。那就是可能是分發服務層或者是Web表現層:

C# (In WCF service layer or in ASP.NET application) 
… //(UnityInstanceProvider.cs in our SampleApp) 
…       
IUnityContainer _container = new UnityContainer; 
… 
 
//This is the only call to UNITY container in the whole solution 
return _container.Resolve(_serviceType); 
…   
           

  關于WCF服務類的更多内容,請看分發服務層的章節。

  主要地,Unity容器檢查所有基于構造函數和setter屬性的依賴。這是依賴注入的基本原則,提供了

在安裝或運作時改變依賴的靈活性。例如,我們在配置檔案裡指定了一個模拟對象來替換真實的資料

通路對象,這樣建立的對象就是CustomerMock而不是CustomerRepository。

2.15.7.- 屬性注入(Setter) 

  為了了解屬性依賴,試想一個ProductService的類,屬性中有着另一個類Supplier的引用。為了強制

依賴對象的注入,需要在屬性上添加依賴屬性,請看下面的代碼:

C#  
 
public class ProductService 
{  
private Supplier supplier;  
 
[Dependency]  
public Supplier SupplierDetails 
{  
    get { return supplier; }  
    set { supplier = value; }  
}  
} 
           

 是以,通過Unity容器建立ProductService類的執行個體,Supplier類的示例會自動建立并賦給ProductService

的屬性。

  檢視更多的Unirty依賴注入的例如,參照下面的檔案

Unity 2.0 Hands On Labs  

http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6932 

Webcast  Demos 

http://unity.codeplex.com/Wiki/View.aspx?title=Webcast%20demos 

MSDN Technical Article &Sample Code 

http://msdn.microsoft.com/en-us/library/cc816062.aspx

2.15.8.-  Unity的主要特性

   Unity提供了以下值得一提的功能:

-  Unity提供了一種機制來建構對象執行個體,可以包括其他對象執行個體。

-  Unity暴露了“RegisterType()”方法,讓我們可以配置容器的類型和對象映射(包括單例執行個體),

   “Resolve()”方法傳回包括依賴對象的示例。

-  Unity提供了控制反轉,可以讓預配置的對象在類中注入。可以指定接口或者類的構造函數,或者

   可以在屬性上使用标志,方法調用或屬性的注入。

-  容器支援層次結構。容器可以有子容器,允許對象的本地查詢從子容器傳到父容器。

-  可以使用标準的配置檔案配置容器。

-  不需要類中有特别定義。類沒有特别的要求,除了需要用方法調用注入和屬性注入時。

-  Unity可以擴充容器的功能;例如,可以實作使用緩存的容器。

2.15.9.- 什麼時候使用Unity 

  依賴注入提供了簡化代碼的機會,自動化處理對象間的抽象依賴和生成依賴對象執行個體。然而,

這對性能有一點影響。另一方面,使用記憶體中的對象時,可能會明顯的影響性能。

  當隻有直接的依賴時複雜性會增加一些。

  Unity可以用在下面的情形:

-  對象和類有着對象對象或類的依賴

-  依賴比較複雜并且需要抽象

-  為了在構造函數,方法或屬性中使用注入

-  為了管理對象執行個體的生命周期

-  為了可以在運作時配置和改變依賴

-  為了使用模拟進行單元測試

-  為了在Web應用中通過回傳(postbacks)緩存和持久化依賴

  不需要使用Unity的情形:

-  對象和類沒有其他對象或類的依賴

-  依賴非常簡單,不需要抽象

3.- DUAL ACCESS TO DATA SOURCES 

  大多數系統中,使用者需要執行多種查詢,排序和過濾,不管是事務還是更新操作。

  為了執行這樣的可視化查詢,可以使用相同的領域邏輯類和相關的資料通路倉儲來進行事務操作。然而,

如果想要達到最高的優化和性能,可能這不是最好的選擇。

  簡言之,為使用者顯示顯示資訊或者并發更新不是領域最重要的行為(并發管理和異常管理)。是以,它

不是為了優化并發管理的自跟蹤的離線實體。所有這些問題最終隻影響查詢的性能,我們希望做到的是

優化查詢的性能。即使有查詢的其他關于安全性等需求,但可以在别處實作。

  當然,如有隻有一個資料源可以通路,無法實作優化性能。是以,“尺有所短寸有所長”。最好把技術用在

它擅長的方面。

  軟體架構師和開發人員在不必要的一些需求上進行靈活的定義是很正常的。這可能是其中的一種情況。隻用

領域模型來顯示資訊是開發人員或架構師強加的,但并不需要這麼做。

  另一方面是,在多使用者系統中,改變不需要立刻讓其他使用者看見。如果這樣,為什麼使用相同的領域,倉儲,

事務資料源來顯示這些?如果不需要這樣的領域行為,為什麼要用到領域呢?例如,使用基于cubes(BI)的

第二資料庫進行查詢效果會更好。

  總之,系統比較好的架構是有兩個或更多的内部資料通路:

領域驅動設計之代碼優先-架構描述 (翻譯)

Dual Data Access 圖

  值得注意的是在該架構中,右面那列隻是用來查詢(報表,清單)。相反,左邊那列是用來資料寫入,事務

執行等等。

  同樣地,擁有不同資料庫的可行性很大取決于應用的類型。然而如果可行,這會是較好的選擇,由于查詢

隻讀資料不會有沖突。最終使每種類型的操作可擴充性和性能的最大化。這種情況下需要進行資料庫間的同步。

  指令查詢的責任分離(CQRS)是基于類似原則一種更先進的架構。我們會在本指南的最後介紹。

4.- 實體層的部署

  層表示表現、業務和資料層在不同機器的分隔,例如伺服器和其他系統。通常的設計模式是基于2層,3層

最終多層。

  2層

  該模式表現了有兩個主要層的基本結構,用戶端層和資料庫伺服器。在典型的Web場景,用戶端在表現層,

業務邏輯一般在同一伺服器,一般通路資料庫伺服器。是以在Web場景中,用戶端層通常包括表現層和業務

邏輯層,為了維護的目的,邏輯層一般在單一用戶端應用。

領域驅動設計之代碼優先-架構描述 (翻譯)

3層

  在3層設計模式中,使用者和他機器上的用戶端應用互動。該用戶端應用和應用層通信,應用層有業務邏輯

和資料通路邏輯。這樣,應用伺服器通路第3層,資料庫伺服器。該模式在富用戶端很場景。

  下面是描述3層部署模型的圖:

領域驅動設計之代碼優先-架構描述 (翻譯)

  多層

  在這種情況下,Web伺服器和應用伺服器是實體分隔的,應用伺服器包括業務邏輯和資料通路層。對于網絡

安全政策的原因,通常是這種分離是在專業的方式,Web伺服器部署在外圍網絡中而通路應用伺服器放在不同

的子網。通常在用戶端層和Web層中有另一個防火牆。

  下圖描述了多層部署模式:

領域驅動設計之代碼優先-架構描述 (翻譯)

選擇層

  實體分隔邏輯層會影響性能,雖然這對于可擴充的分布負載的不同伺服器是有益的。分隔應用的敏感元件

可以提高安全性。然而,需要考慮的是增加層會增加部署的複雜性,有時會影響性能,是以不要有不必要的層。

   在許多情況下,應用中的所有代碼都應在同一或負載均衡的伺服器上。當你使用遠端通信時,性能會影響

通信,資料也需要序列化來在網絡上傳輸。然而,有些情況下我們可能因為安全層限制或擴充性的要求分割功能

到不用的層。

如果是下面的情況選擇2層模式:

-  Web應用:目标是開發典型的Web應用,具有高性能沒有網絡安全的限制。如果要增加可擴充性,Web伺服器

   應克隆到多個負載均衡平衡伺服器。

-  用戶端伺服器應用(CS應用)。目标是開發直接通路資料庫的用戶端伺服器應用。情況有很多種,由于所有

  的邏輯層都放在用戶端,該用戶端可以是電腦。當有高性能要求時這種結構很有用,然而,CS程式架構有很

  多可擴充性,維護和故障檢測問題,因為所有的業務邏輯和資料通路都在使用者電腦上,每個終端使用者控制着

  配置。這種情況在大多數場景中不推薦。

如果是下面的情況選擇3層模式:

-  目标是開發在使用者機器上有遠端用戶端通路的應用,應用伺服器上釋出有業務邏輯的Web服務。

-  所有的應用伺服器都在同一網絡中。

- “内網”應用程式,安全性的需求不要求表現層和業務層,資料通路層分割。

-  目标是開發典型的最大化性能的Web應用。

如果是下面的情況選擇多層模式:

-  當有業務邏輯不能部署在表現層部署的外圍網絡中的安全性需求時。

-  有非常多的代碼(使用很多伺服器資源),需要增加可擴充性,并且這些業務元件和其他層是分開的。