天天看點

結合領域驅動設計的SOA分布式軟體架構

引言

本文主要是參考Martion Fowler所著的《企業應用架構模式》與Eric Evans所著的《領域驅動設計》這兩本泰山之作,加上本人在近年實際的工作過程中開發SOA系統所認識到的問題所寫的一篇文章,歡迎各位點評。

目錄

<a href="http://blog.51cto.com/2689556/742582?#t1">一、SOA與DDD的定義</a>

<a href="http://blog.51cto.com/2689556/742582?#t2">二、DDD的分層結構</a>

<a href="http://blog.51cto.com/2689556/742582?#t3">三、把業務關系轉化為領域模型</a>

<a href="http://blog.51cto.com/2689556/742582?#t4">四、細說Repository</a>

<a href="http://blog.51cto.com/2689556/742582?#t5">五、領域層的服務</a>

<a href="http://blog.51cto.com/2689556/742582?#t6">六、工廠模式Factory</a>

<a href="http://blog.51cto.com/2689556/742582?#t7">七、細說應用層</a>

<a href="http://blog.51cto.com/2689556/742582?#t8">八、系統總體架構 </a>

一、SOA與DDD的定義

SOA與DDD都是常用的系統架構,但兩者之間所針對的核心是不同的。

SOA(面向服務架構)由Gartner 在1996年提出來,它是一種分布式的軟體架構,它可以根據需求通過網絡對松散耦合的粗粒度應用元件進行部署、組合和使用。簡單來說,SOA就是一種大型 系統開發的體系架構,在基于SOA架構的系統中,具體應用程式的功能是由一些松耦合并且具有統一接口的元件(也就是service)組合建構起來的,它是 針對多核心多平台之間的資料交換。

DDD(領域驅動設計)由Eric Evans在2004提出,它的核心内容是“如何将業務領域概念映射到軟體工程當中”。它推翻了“軟體從資料層開發設計”的舊習慣,強調領域模型在軟體中發揮的強大力量,注重如何把企業内部複雜的業務流程轉化為軟體。

也許可以認為SOA針對的是大型系統的總體架構,注重如何把系統進行項目分離,隔離開發,最後實作系統合并。而DDD是針對單個項目的開發管理過 程,注重如何利用領域模型把業務需求轉化為軟體。兩者之間并沒有存在理論上的沖突,能把兩者結合,各展所長,更能發揮各自的優勢。

<a href="http://blog.51cto.com/2689556/742582?#t0">回到目錄</a>

二、DDD的分層結構

1. 概念

從概念上來說,領域驅動設計架構主要分為基礎設施層、領域層、應用層、表現層4個概念層。

基礎結構層:是為各層提供各項通用技術能力而建構的,它可以為領域層提供像Hibernate、LINQ、ADO.NET等持久化機制,為應用層傳遞消息,為表現層提供插件等等。

領域層:它是系統的核心部分,代表業務的邏輯概念。它會根據業務的實際流程定制了業務資訊以及業務規則,并按一定的關系制定領域模型。領域模型盡管 需要依賴基礎結構層進行儲存,但領域模型之間的邏輯關系是跟基礎結構層相隔離的。即使基礎結構層從NHibernate技術轉換成LINQ技術,也不會影 響到領域層的結構。領域模型隻會依賴實際的業務邏輯,它隻會根據業務的轉變而靈活變動。

應用層:它的任務是協調領域層與表現層之間的關系,也可以作為系統與外界溝通的橋梁,在這層裡面不會包括任何的業務邏輯。在SOA面向服務架構,這一層起着重要的作用,在第七節将詳細說明。

表現層:它是常用的界面開發,可以以頁面(ASP.NET、JSP),視窗(WinForm、WPF、Swing)等形式表現,它的主要職責是負責 與使用者進行資訊溝通。(注意:在一般的項目開發中,Web服務會作為與外界通訊的接口放置在表現層中,但在SOA中,Web服務會大多置于應用層中,下面 将會作進一步解釋)

2. 開發執行個體

在此先舉個常見的訂單管理例子,在下面的章節裡都會以這個執行個體為參考:

每個使用者在Person表裡面都會有一個對應的帳戶,裡面記錄了使用者的姓名、位址、電話、積分(Point)等基本資訊。

在Order表裡記錄的是每次交易的過程,每次商品的送貨費(Freightage)為10元,當商品價格(Price)超過98元可免費送貨,當 使用者Person積分(Point)超過2000分可獲7折優惠(Favorable),1000~2000分可獲8折,1000分以下可有9折,最後總 體價格為為(TotalPrice)。

在最後結單的時候Order表裡會産生訂單号碼OrderNumber和下訂日期Delivery,Person表的積分也會加上訂單總價的點數。

最後OrderItem表包含了物品Goods、物品價格Price、購買數量Count等屬性,它主要記錄每次訂單的詳細交易狀況。

上面的業務邏輯跟淘寶、當當等等大型購物網基本相似。之是以用這樣一個例子作為參考,是想表現一下DDD是如果利用領域模型去适應多變的業務邏輯關系。

三、把業務關系轉化為領域模型

模型驅動設計設計(MODEL-DRIVEN-DESIGN)是DDD裡面的核心,它代表的是各個對象之間的關系,把複雜的邏輯關系轉化為模型。

模型主要分為實體(Entity)、值對象(Value Object)與服務(Service)三種。

實體:實體所包含的不單止是一連串的屬性,更重要的是與事件的聯系,在一個生命周期中環境的變化與事件發生,将引起實體内部産生變化。好像在實體 Order裡面,Person的積分(Point)和OrderItem的價格(Price)都會直接影響總體價格(TotalPrice)的大小,而總 體價格也會影響到運費Freightage的多少等等。在Order實體的一切,都會受到Person、OrderItem等這些外部因數的影響,這樣的 對象被視為實體。在不同的時刻,實體會有不同的狀态,是以在開發過程中我們需要為實體加上一個“辨別符”來區分對象的身份,它是實體的生命周期裡的唯一标 志。

值對象:當所用到的對象隻有屬性而沒有其他邏輯關系的時候,我們就可以把它視為是值對象。值對象沒有狀态,也不需要有 “辨別符”。在多數情況下它可以作為一個屬性存在于一個實體的内部。一般情況下值對象的屬性是不可改變的,當需要更改屬性時,可以把整個對象删除,然後重 新加入一個新對象。

服務:當實體之間存在某些操作,它們并不單一地附屬于某一個實體,而是跟多個實體都有關聯的時候,就可以使用服務來封裝這些操作。值得注意的是服務并非單獨指Web Service, 也并非單單存在于領域層, 而是在各個層當中都會存在服務,每一層的服務都有着不同的職能。在基礎結構層服務可能是用于建構身份驗證、電子郵件、錯誤處理等等操作;在領域層,服務更 多時候是一種操作,它用于協調多個實體之間的關系,處理各類的業務問題;在應用層(特别是在分布式開發系統内),服務多以Web Service、TCP/IP套接字、MSMQ等等方式實作,服務在此處會作為一個與外界通訊的接口;

備注 :這裡面也存在一定的争義,Eric 認為實體所代表的隻是多個對象之間的關系,而它們的動作将會由服務來展現出來,這被稱為貧血型模型。但在開發過程中,越來越多人會把動作加入到實體裡面, 這被稱為充血型模型。其實不同的問題應該客觀分析,分别對待,在這個例子裡面将會以按照 Eric 的定義來開發服務,在後面的開發過程中大家也可以從中展現一下服務層所帶來的好處。

2. 執行個體說明

先以ADO.NET Entity Framework實作模型,Person、Order分别屬于兩個實體,它們都将繼承Root接口,在它們的生命周期内都會生成一個Guid作為标志。 此處把OrderItem作為一個值對象置于Order實體内,這意味着OrderItem會通過Order來擷取,外界不能跨越Order直接擷取 OrderItem。當然這應該由具體的業務情況來确定,當外界需要單獨調用OrderItem類的時候,就應該考慮把OrderItem獨立成為一個實 體類。

 四、細說Repository

1.概念

Repository是把持久化對象轉換成領域模型的一種方式,可用于擷取、更新持久對象并管理它們的生命周期。它使應用程式與持久化技術實作解 耦,程式無需受制于使用Oracle還是MySql資料庫,也不會受到Hibernate、LINQ、ADO.NET等資料層的限制,使開發人員可以把注 意力集中到領域模型當中。

Repository與傳統三層模式的DAL層有點相似,但Repository針對的是每一個根對象來劃分邊界的。在這個例子當中,Person 與Order都會有對應的PersonRepository、OrderRepository。而OrderItem隻是Order的子屬性,是以它的插 入、更新、删除都會包含在OrderRepository當中。當多個對象之間建立起聯系後,關系将是複雜的,特别是在LINQ裡面,程式可以輕易通過 Person的導航屬性裡擷取OrderItem的值,最後很容易使代碼變得混亂。是以确立Repository的邊界,可以在有效管理每個 Repository的職能。

2.執行個體說明

注意OrderItem的存取、删除都包含在OrderRepository裡面。在擷取、修改Order的時候,也會利用“顯式加載” context.Order.Include("OrderItem") 的方法,使OrderItem實作同步更新。而通過PersonRepository.GetPerson(int )擷取的Person對象,它内部的Order屬性将是null值,這必須清晰按照領域模型的邊界劃分的。

當LINQ面世以後,資料的擷取變得簡單,特别在一些小型的系統開發時,很多人會不自覺地把這種領域模型的分界規則打破。但随着系統的複雜化,問題 就會逐漸呈現。比如當Order對象的屬性被更新,使用OrderRepository.Update(Order)更新資料庫後,頁面的Person對 象未能同步實作更新,在Person與資料庫交換資料的時候,Order又被變回舊值。

在混亂的資料層開發中,這種情況非常常見,是以在下會堅持Repository的原則,把Repository的職能清晰按照領域模型劃分。

在更新Order這種複雜的領域模型時,如果要分辨單個OrderItem屬性是建立值還是更新值,然後分别處理,那将是比較麻煩的,而且 OrderItem隻是一個值對象,ID編碼等屬性對它沒有任何實在意義。是以在更新List&lt;OrderItem&gt;屬性時都會先把它全部删 除,然後重新加載,在OrderItem數量不多的時候,這是一種十分有效的方法。

五、領域層的服務

1. 例子說明

在第二節已基本介紹過服務的作用了,領域層服務的作用主要是為了解決業務上的邏輯問題,更多的時候,服務是一個與業務相關的動作。比如在上述例子中:

在Order表裡記錄的是每次交易的過程,每次商品的送貨費(Freightage)為10元,當商品價格(Price)超過98元可免費送貨,當 使用者 Person積分(Point)超過2000分可獲7折優惠(Favorable),1000~2000分可獲8折,1000分以下可有9折,最後總體價 格為為(TotalPrice)。

這複雜的業務邏輯,完全可以由一個領域服務類AccountManager來完成

你可能會說,在這個業務流程中,除了積分優惠Person.Point以外,其他的業務都隻與Order的屬性有關,按照充血型模型的方案,完全可 以把這些業務放到Order的方法當中,而把積分優惠獨立成為一個服務。但在下在很多的開發過程中發現,為模型附上動作會帶來一連串的問題,好像你不知道 哪些操作應該在模型動作上實作,哪裡應該在服務中實作......。對于這些無休止的争論不會因為這裡的一個小例子而停止,但在這裡我會堅持使用貧血型模 型,利用服務來完成所有的動作。

再舉一個例子:在最後結單的時候Order表裡會産生訂單号碼OrderNumber和下訂日期Delivery,Person表的積分也會加上訂單總價的點數。對應這個操作,也可以單獨開發一個PaymentManager服務類進行管理。

利用領域層的服務,使得每個Manager服務類的職能非常明确,業務管理起來也十分地友善,領域層可以随着業務的改變而靈活變動。而且領域層具有 “高内聚,低耦合” 特性,它并不依賴其它任何一層,而隻是把業務邏輯包含在裡面。

六、工廠模式Factory

Factory是常用到軟體開發模式,在網上像簡單工廠、工廠方法、抽象工廠等開發模式的資料都到處可尋,可這并不是領域驅動設計的主題。在這一節裡,我主要想介紹Factory的适用時機。

并非生成所有對象的時候,都需要用到工廠模式。在生成簡單對象的時候,可以直接利用構造函數來代替工廠,也可以添加工廠方法來生成對象。但如果在生 成對象時,内部屬性之間存在一系統複雜的業務規則的時候,就可以把生成方法獨立到一個Factory類裡面。這時候用戶端無需理會潛在邏輯關系,而直接通 過這個Factory來生成相應的對象。

舉個例子,在建立Order的時候,業務上規定運費是總體金額的1%,折扣規定是7.5折...... 。如果由用戶端建立一個對象Order,然後為這些屬性負值,那相關的業務邏輯就會暴露在外。這時候就可以使用Factory模式,把屬性之間的關系封裝 到Factory之内,用戶端通過Factory就能輕松地生成Order對象而無需要理會複雜的内部關系。

至于較複雜的Factory模式,在此不多作介紹,各位可以在網上查找相關資料。

七、細說應用層

1. SOA系統中應用層的特點

在開發SOA分布式系統的時候,應用層是一個重點,它主要有兩個作用。

第一,應用層主要作用是協調領域層工作,指揮領域對象解決業務問題,但應用層本身不會牽扯到業務狀态。

第二,在SOA系統當中應用層是資料運輸中心和資訊發放的端口,擔負着資料轉換與資料收發的責任。

它有以下的特點:

粗粒度

分布式系統與普通網站和應用程式不同,因為它假定外界對系統内部是毫無了解的,使用者隻想輸入相關資料,最後得到一系列計算結果。是以我們應該把計算結果封裝在一個資料傳輸對象(DTO)内,實作粗粒度的傳遞,這是一般項目與SOA系統在服務層的一個最明顯的差别。 想想如果一個頁面需要同時顯示一個顧客的個人資料、某張訂單的詳細資料,那将要同時擷取Person、Order、OrderItem三張表的資訊。在普 通系統的開發過程中,這并不會造成太大問題,但在使用遠端服務的時候,如果用三個方法分别擷取,那将會造成不少的性能損耗。特别是在分布式開發系統中,應 用層與表現層之間是實作分離的,更多時候兩者是由不同部門所開發的子產品,表現層不會了解應用層中的邏輯關 系,Person,Order,OrderItem三樣東西在表現層看來,也就是同一樣東西,那就是傳回值。是以在系統内,應該把多張表的資訊封裝在一個 DTO對象内,通過應用層一個遠端方法一次性返還。使用粗粒度的資料元素是分布式系統的一個特點。

傳輸性

如果你熟悉SOA系統,對DTO(Data Transfer Object 資料傳輸對象)這個詞一定并不陌生。DTO屬于一個資料傳輸的載體,内部并不存在任何業務邏輯,通過DTO可以把内部的領域對象與外界隔離。DTO所封裝 的是用戶端的資料,是以它的設計更多地是針對用戶端的需求,而不是業務邏輯。比如說本來Person與Order是一對多的關系,但當一個頁面隻要顯示的 是一個客戶的單張訂單資訊,那我們就可以根據需要把DTO中的Person和Order設計為一對一的關系。如果你是使用MVC開發一般的網站,更多時候 會把傳回對象直接轉化為Model。如果你開發是一個分布式系統,那更多時候會從系統性能與隐藏業務邏輯出發着想。而且考慮到把内部對象轉化為DTO,将 是一件麻煩的事,建議應該考慮DTO的相容性,使DTO可以作為多個方法的返還載體。(注意:在SOA系統内,應該從性能出發優先考慮粗粒度元素的傳輸性 問題)

封裝性

在SOA系統當中應用層服務的釋出并不需要複雜的模型,隻需使用外觀模式(Facade)把一些功能封裝在少數的幾個服務類裡面,使用Web Service、TCP/IP套接字、MSMQ等服務方式向外界釋出。

說到這裡,我真的十分感激Martin先生帶給我的幫助,在開發過程中,這些複雜的問題帶給我不少的困擾,Martin先生一紙富有經驗的獨特見解,真的帶給在下很大的啟發。

2. 應用層的協調性

應用層服務會利用Repository,完成實體基本的插入、更新、擷取等等操作,并調用領域層的服務管理的業務邏輯。注意觀察,一切的業務邏輯都隻會隐藏于領域層,應用層服務隻起着協調作用,本身不應該包含有任何業務邏輯。

可以看到OrderService就是通過調用AccountManager、PaymentManager等領域層服務來完成結賬、付款等一系列複雜業務邏輯的。

3. 資料轉換過程

前面已經解釋了DTO的作用,但實作領域對象與DTO之間的轉換是一件複雜的事件,是以可以建立一個資料轉換器實作此功能。

在平常的工作裡,不太多會把“訂單管理系統”做成SOA的模式,因為在分布式系統中,資料的格式與定義大多數由部門之間協定,其中包含明确的規則。 但由于條件的局限,在這裡還是想以訂單管理為例子,希望可以帶給你一定的幫助。例子如下:在購物車結賬,頁面會包含使用者基本資訊,目前訂單資訊,訂單明細 資訊等多個部分。

要完成資料轉換,首先可以根據頁面建立DTO對象,在分布式系統中,通常會把DTO對象放在一個獨立的命名空間裡,在這個執行個體裡面稱之為 Business.TransferObject。DTO對象更多時候是面向表現層的需求而建立,這裡由于表現層頁面所需要的隻是單個使用者,單張訂單的數 據,是以在OrderDTO對象裡會包含了使用者資訊和訂單資料,也存在訂單詳細列List&lt;OrderItemDTO&gt;。當然,DTO的設計 可以随着需求而修改。

在SOA系統裡,DTO是遠端服務資料的載體,是以會把DTO附上可序列化特性,這此例子中會使用WCF的資料契約實作OrderDTO和OrderItemDTO。

如圖,要實作資料轉換,就應該建立資料轉換器。在這裡OperationAssembler就是一個資料轉換器,它是資料轉換的核心,它是領域對象 與DTO之間實作轉換的工具。要在多個對象之間實作資料轉換實在是一件非常麻煩的事,是以我一直提倡注意DTO對象的相容性,使單個DTO對象可以适用于 多個外觀層,以減少資料轉換所帶來的麻煩。

通過資料轉換器,可以順利實作領域模型與DTO之間的轉換,協調應用層服務的運作。

4. 應用層的釋出

在開發SOA系統的時候,應用層的服務需要使用遠端方法對外開放,在接收到請求的時候,它可以調用領域層服務擷取運算結果,然後通過資料轉換器 OperationAssembler把運算結果轉換成DTO,最後返還到表現層。在起初,我曾嘗試對應每個應用層的對象建立一個遠端接口,但經過多次重 構以後,我覺得行程對象就是一個簡單的對外接口,對象之間不存在什麼邏輯關系。是以更簡單的方法是使用外觀模式,建立少數的幾個遠端服務類,把所有的應用 層對象的方法都包含在内。

可以留意代碼,OperationService包括了對Person模型和Order模型的所有操作。而且每個操作都隻是簡單地調用應用層服務 (ApplicationService) 獲得計算結果,然後使用資料轉換器 (OperationAssembler)轉換資料,當中并不存在任何的業務邏輯。

八、系統總體架構

1. 展現領域驅動設計的架構

到此總結一下領域驅動設計DDD的總體結構,Repository層使用ORM映射或SQL指令等方式把持久化資料轉化為領域對象,然後根據業務邏 輯設計對應領域層服務Domain Service 。接着應用層進行操作上的協調,利用Repository、領域模型、領域層服務Domain Service 完成業務需要,再通過資料轉換器把領域對象Domain Object轉化為資料傳輸對象DTO。最後,利用遠端通訊技術把應用層的服務(Application Service)對外開放。

注意留意的是SOA系統中,UI表現層與Application Service應用層服務是實作分離的,表現層可以同時調用多方的遠端服務來完成工作。

2. 展現面向服務開發的架構

面向服務開發SOA的架構主要展現在表現層與應用層之間通過遠端通訊實作分離,表現層可以引用多方的應用服務作為基礎。由此系統實作業務上的分離, 不同的功能子產品可以獨立開發,最後通過服務在表現層共同展現。長期的發展,使不少的企業針對單個功能子產品開發出一套獨立的系統,再通過強大的虛拟化技術為 第三方提供服務,這就是雲計算的前身。

就像一個通訊購物的平台,其實就是綜合了内部業務管理、銀行轉帳服務、呼叫中心、第三方接口等多方服務的綜合性平台。如果你有過這方面的經驗,就會 知道其實銀行轉帳、呼叫中心不過就是銀行、電信、移動等公司提供的幾個簡單的接口。開發人員根本無需理會其實内部的結構,隻要通過幾個簡單的遠端方法就能 調用。這正是應用層服務 Application Service 的最好展現。

3. 結束語

寫這篇文章目的隻是想與各位分享一下我在開發過程中的一些體會,歡迎各位點評,指出其中的不足。

其實架構是死物,人才是有腦子的生物。每一個架構必然會有其優點,也會有不足之處,我們應該從開發之中一齊起來體驗,而不是盲目地跟從,希望在下的拙見能夠給大家帶來幫助。可别忘了支援一下,挺一挺。

相關文章

<a href="http://www.cnblogs.com/leslies2/archive/2011/01/26/1934162.html" target="_blank">SOA的概念</a>

<a href="http://www.cnblogs.com/leslies2/archive/2011/03/29/1997889.html" target="_blank">SOA基本架構</a>

<a href="http://www.cnblogs.com/leslies2/archive/2011/12/12/2272722.html#">結合領域驅動設計的SOA分布式軟體架構</a>

作者:風塵浪子

<a href="http://www.cnblogs.com/leslies2/archive/2011/12/12/2272722.html">http://79100812.blog.51cto.com/2689556/742582</a>

<a href="http://www.cnblogs.com/leslies2/archive/2011/11/24/2257572.html">原創作品,轉載時請注明作者及出處</a>

本文轉自 leslies2  51CTO部落格,原文連結:版權聲明:原創作品,如需轉載,http://blog.51cto.com/79100812/742582