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

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

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應用。

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章