结合领域驱动设计的SOA分布式软件架构
引言
本文主要是參考Martion Fowler所著的《企業應用架構模式》與Eric Evans所著的《領域驅動設計》這兩本泰山之作,加上本人在近年實際的工作過程中開發SOA系統所認識到的問題所寫的一篇文章,歡迎各位點評。
最后兩節? 細說應用層 、系統總體架構?是本文的重點,著重說明領域驅動設計與SOA之間的關系,對DDD有一定基礎的朋友可以越過前面的幾節,直接查看第七、八節。
源代碼下載 (數據庫可以在.edmx文件根據模型生成)
?
目錄
一、SOA與DDD的定義
二、DDD的分層結構
三、把業務關系轉化為領域模型
四、細說Repository
五、領域層的服務
六、工廠模式Factory
七、細說應用層
八、系統總體架構?
?
?
一、SOA與DDD的定義
SOA與DDD都是常用的系統架構,但兩者之間所針對的核心是不同的。
SOA(面向服務架構)由Gartner 在1996年提出來,它是一種分布式的軟件架構,它可以根據需求通過網絡對松散耦合的粗粒度應用組件進行部署、組合和使用。簡單來說,SOA就是一種大型系統開發的體系架構,在基于SOA架構的系統中,具體應用程序的功能是由一些松耦合并且具有統一接口的組件(也就是service)組合構建起來的,它是針對多核心多平臺之間的數據交換。
DDD(領域驅動設計)由Eric Evans在2004提出,它的核心內容是“如何將業務領域概念映射到軟件工程當中”。它推翻了“軟件從數據層開發設計”的舊習慣,強調領域模型在軟件中發揮的強大力量,注重如何把企業內部復雜的業務流程轉化為軟件。
也許可以認為SOA針對的是大型系統的總體架構,注重如何把系統進行項目分離,隔離開發,最后實現系統合并。而DDD是針對單個項目的開發管理過程,注重如何利用領域模型把業務需求轉化為軟件。兩者之間并沒有存在理論上的沖突,能把兩者結合,各展所長,更能發揮各自的優勢。
回到目錄
二、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是如果利用領域模型去適應多變的業務邏輯關系。
回到目錄?
三、把業務關系轉化為領域模型
1. 概念
模型驅動設計設計(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獨立成為一個實體類。
在這里可利用分部類為實體增加Guid屬性,關于分部類于分部方法的詳細介紹可參考C#綜合揭秘——分部類和分部方法
namespace Business.DomainModel{
public interface Root {
}
public partial class Order:Root
{
private Guid _guid;
public Order()
{
_guid = System.Guid.NewGuid();
}
//為根對象設置唯一的Guid;
public Guid GUID
{
get { return _guid; }
}
}
public partial class Person:Root
{
public Person()
{
_guid = System.Guid.NewGuid();
}
//為根對象設置唯一的Guid;
private Guid _guid;
public Guid GUID
{
get { return _guid; }
}
}
}
回到目錄?
?四、細說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的職能清晰按照領域模型劃分。
namespace Business.IRepository{
public interface IOrderRepository
{
Order GetOrder(int id);
IList<Order> GetList();
IList<Order> GetListByPerson(int personID);
int AddOrder(Order order);
int DeleteOrder(int id);
int UpdateOrder(Order order);
int AddOrderItem(OrderItem orderItem);
int DeleteOrderItem(int id);
}
public interface IPersonRepository
{
int AddPerson(Person person);
int AttachPerson(Person person);
int UpdatePerson(Person person);
Person GetPerson(int id);
IList<Person> GetList();
}
}
namespace Business.Repository
{
public class OrderRepository:IOrderRepository
{
//根據ID獲取單個Order
public Order GetOrder(int id)
{
BusinessContext _context = new BusinessContext();
Order order = null;
try
{
using (TransactionScope scope = new TransactionScope())
{
//由于OrderItem是Order實體中的一個屬性,必須通過OrderRepository同步獲取
var list = _context.Order.Include("OrderItem")
.Where(x => x.ID == id);
if (list.Count() > 0)
order = list.First();
else
order = new Order();
scope.Complete();
}
}
catch (Exception ex)
{
//出錯處理,并返回一個空對象
Business.Common.ExceptionManager.DataException.DealWith(ex);
order = new Order();
}
_context.Dispose();
return order;
}
..................
..................
}
public class PersonRepository:IPersonRepository
{
public int AddPerson(Person person)
{
return LinqHelp.Add<Person>(person);
}
public Person GetPerson(int id)
{
return LinqHelp.Get<Person>(id);
}
.................
.................
}
}
在更新Order這種復雜的領域模型時,如果要分辨單個OrderItem屬性是新建值還是更新值,然后分別處理,那將是比較麻煩的,而且OrderItem只是一個值對象,ID編碼等屬性對它沒有任何實在意義。所以在更新List<OrderItem>屬性時都會先把它全部刪除,然后重新加載,在OrderItem數量不多的時候,這是一種十分有效的方法。
namespace Business.Repository{
public class OrderRepository:IOrderRepository
{
.................
.................
//更新Order,因為難以別哪些是原有的OrderItem,哪些OrderItem是新插入
//使用簡單的方法,會先把原有的OrderItem的刪除,再重新插入
public int UpdateOrder(Order order)
{
int returnValue = -1;
BusinessContext _context = new BusinessContext();
try
{
using (TransactionScope scope = new TransactionScope())
{
var list = _context.Order.Include("OrderItem")
.Where(x => x.ID == order.ID);
if (list.Count() > 0)
{
//更新Order列
Order _order = list.First();
_order.Count = order.Count;
_order.Delivery = order.Delivery;
_order.Favorable = order.Favorable;
_order.Freightage = order.Freightage;
_order.OrderNumber = order.OrderNumber;
_order.PersonID = order.PersonID;
_order.Price = order.Price;
_order.TotalPrice = order.TotalPrice;
//刪除原有的訂單明細項OrderItem
if (list.First().OrderItem.Count != 0)
foreach (var item in list.First().OrderItem)
DeleteOrderItem(item.ID);
//加入新的訂單明細項OrderItem
if (order.OrderItem.Count != 0)
{
foreach (var item in order.OrderItem)
{
var _orderItem = new OrderItem();
_orderItem.Count = item.Count;
_orderItem.Goods = item.Goods;
_orderItem.OrderID = item.OrderID;
_orderItem.Price = item.Price;
AddOrderItem(_orderItem);
}
}
returnValue = _context.SaveChanges();
}
else
returnValue = 0;
scope.Complete();
}
}
catch (Exception ex)
{
Business.Common.ExceptionManager.DataException.DealWith(ex);
returnValue=-1;
}
_context.Dispose();
return returnValue;
}
//插入OrderItem
public int AddOrderItem(OrderItem orderItem)
{
return LinqHelp.Add<OrderItem>(orderItem);
}
//刪除OrderItem
public int DeleteOrderItem(int id)
{
EntityKey key = new EntityKey("BusinessContext.OrderItem", "ID", id);
return LinqHelp.Delete(key);
}
}
}
回到目錄?
五、領域層的服務
1. 例子說明
在第二節已基本介紹過服務的作用了,領域層服務的作用主要是為了解決業務上的邏輯問題,更多的時候,服務是一個與業務相關的動作。比如在上述例子中:
在Order表里記錄的是每次交易的過程,每次商品的送貨費(Freightage)為10元,當商品價格(Price)超過98元可免費送貨,當用戶 Person積分(Point)超過2000分可獲7折優惠(Favorable),1000~2000分可獲8折,1000分以下可有9折,最后總體價 格為為(TotalPrice)。
這復雜的業務邏輯,完全可以由一個領域服務類AccountManager來完成
namespace Business.Service.DomainService{
public class AccountManager
{
private Person _person;
private Order _order;
public AccountManager(Person person, Order order)
{
_person = person;
_order = order;
}
///計算總體收費
public void Account()
{
//計算商品數量
GoodsCount();
//計算商品價格
PriceAccount();
//計算優惠等級
FavorableAccount();
double price1 = (_order.Price - _order.Favorable).Value;
//計算運費
FreightageAccount(price1);
//計算總體價費
_order.TotalPrice = price1 + _order.Freightage.Value;
}
//計算商品數量
private void GoodsCount()
{
_order.Count=0;
foreach (var OrderItem in _order.OrderItem)
_order.Count += OrderItem.Count;
}
//商品總體價格
private void PriceAccount()
{
_order.Price = 0;
foreach (var OrderItem in _order.OrderItem)
_order.Price += OrderItem.Price * OrderItem.Count;
}
//優惠分為三等,積分小于1000有9折,小于2000分為8折,大于2000為7折
private void FavorableAccount()
{
int point = (int)_person.Point.GetInt();
if (point < 1000)
_order.Favorable = _order.Price * 0.1;
if (point >= 1000 && point < 2000)
_order.Favorable = _order.Price * 0.2;
if (point > 2000)
_order.Favorable = _order.Price * 0.3;
}
//如果價格在98元以上,可免運費。其余運費為10元
private void FreightageAccount(double price)
{
if (price >= 98)
_order.Freightage = 0;
else
_order.Freightage = 10;
}
}
}
你可能會說,在這個業務流程中,除了積分優惠Person.Point以外,其他的業務都只與Order的屬性有關,按照充血型模型的方案,完全可以把這些業務放到Order的方法當中,而把積分優惠獨立成為一個服務。但在下在很多的開發過程中發現,為模型附上動作會帶來一連串的問題,好像你不知道哪些操作應該在模型動作上實現,哪里應該在服務中實現......。對于這些無休止的爭論不會因為這里的一個小例子而停止,但在這里我會堅持使用貧血型模型,利用服務來完成所有的動作。
再舉一個例子:在最后結單的時候Order表里會產生訂單號碼OrderNumber和下訂日期Delivery,Person表的積分也會加上訂單總價的點數。對應這個操作,也可以單獨開發一個PaymentManager服務類進行管理。
namespace Business.Service.DomainService{
public class PaymentManager
{
//下單結算
public void Payment(Order order,Person person)
{
//確定下單,建立訂單號
order.OrderNumber = Guid.NewGuid().ToString();
order.Delivery = DateTime.Now;
//增加積分
if (person.Point.HasValue)
person.Point += (int)order.TotalPrice.GetValueOrDefault();
else
person.Point = (int)order.TotalPrice.GetValueOrDefault();
}
}
}
利用領域層的服務,使得每個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等領域層服務來完成結賬、付款等一系列復雜業務邏輯的。
namespace Business.Service.ApplicationService{
public class PersonService
{
private IPersonRepository personRepository = DataAccess.CreatePersonRepository();
public int AddPerson(Person person)
{
return personRepository.AddPerson(person);
}
public int UpdatePerson(Person person)
{
return personRepository.UpdatePerson(person);
}
public Person GetPerson(int personID)
{
return personRepository.GetPerson(personID);
}
public IList<Person> GetList()
{
return personRepository.GetList();
}
}
public class OrderService
{
private IOrderRepository orderRepository = DataAccess.CreateOrderRepository();
public int AddOrder(Order order)
{
//計算Order總體費用
Account(order);
//加入修改后的Order
return orderRepository.AddOrder(order);
}
//調用領域層服務AccountManager,計算Order總體費用
private void Account(Order order)
{
//獲取對應Person對象
IPersonRepository personRepository = DataAccess.CreatePersonRepository();
Person person = personRepository.GetPerson(order.PersonID);
//調用服務層的AccountManager對象,計算費用,修改Order
AccountManager accountManager = new AccountManager(person, order);
accountManager.Account();
}
//調用領域層服務PaymentManager,確認訂單
public Order Payment(int orderID)
{
var order=orderRepository.GetOrder(orderID);
if (order != null)
{
PersonRepository personRepository = new PersonRepository();
var person=personRepository.GetPerson(order.PersonID);
PaymentManager paymentManager = new PaymentManager();
paymentManager.Payment(order, person);
orderRepository.UpdateOrder(order);
personRepository.UpdatePerson(person);
return order;
}
else
throw new Exception("Can not find order!");
}
public int DeleteOrder(Order order)
{
return orderRepository.DeleteOrder(order.ID);
}
public Order GetOrder(int orderID)
{
return orderRepository.GetOrder(orderID);
}
public IList<Order> GetList()
{
return orderRepository.GetList();
}
public IList<Order> GetListByPerson(int personID)
{
return orderRepository.GetListByPerson(personID);
}
public int UpdateOrder(Order order)
{
Account(order);
return orderRepository.UpdateOrder(order);
}
public int AddOrderItem(OrderItem orderItem)
{
int index = orderRepository.AddOrderItem(orderItem);
Order order = orderRepository.GetOrder(orderItem.OrderID);
UpdateOrder(order);
return index;
}
public int DeleteOrderItem(OrderItem orderItem)
{
int index = orderRepository.DeleteOrderItem(orderItem.ID);
Order order = orderRepository.GetOrder(orderItem.OrderID);
UpdateOrder(order);
return index;
}
.......................
.......................
}
}
?
3. 數據轉換過程
前面已經解釋了DTO的作用,但實現領域對象與DTO之間的轉換是一件復雜的事件,因此可以建立一個數據轉換器實現此功能。
在平常的工作里,不太多會把“訂單管理系統”做成SOA的模式,因為在分布式系統中,數據的格式與定義大多數由部門之間協定,其中包含明確的規則。但由于條件的局限,在這里還是想以訂單管理為例子,希望可以帶給你一定的幫助。例子如下:在購物車結賬,頁面會包含用戶基本信息,當前訂單信息,訂單明細信息等多個部分。
?
?
要完成數據轉換,首先可以根據頁面建立DTO對象,在分布式系統中,通常會把DTO對象放在一個獨立的命名空間里,在這個實例里面稱之為Business.TransferObject。DTO對象更多時候是面向表現層的需求而建立,這里由于表現層頁面所需要的只是單個用戶,單張訂單的數據,所以在OrderDTO對象里會包含了用戶信息和訂單資料,也存在訂單詳細列List<OrderItemDTO>。當然,DTO的設計可以隨著需求而修改。
在SOA系統里,DTO是遠程服務數據的載體,所以會把DTO附上可序列化特性,這此例子中會使用WCF的數據契約實現OrderDTO和OrderItemDTO。
?
?
如圖,要實現數據轉換,就應該建立數據轉換器。在這里OperationAssembler就是一個數據轉換器,它是數據轉換的核心,它是領域對象與DTO之間實現轉換的工具。要在多個對象之間實現數據轉換實在是一件非常麻煩的事,所以我一直提倡注意DTO對象的兼容性,使單個DTO對象可以適用于多個外觀層,以減少數據轉換所帶來的麻煩。
namespace Business.Service.ApplicationService{
public class OperationAssembler
{
//把領域對象轉換成DTO
public static OrderDTO GetOrderDTO(Order order,Person person)
{
OrderDTO orderDTO = new OrderDTO();
if (person != null)
{
orderDTO.EMail = person.EMail.GetString();
orderDTO.Address = person.Address.GetString();
orderDTO.Name = person.Name.GetString();
orderDTO.PersonID = person.ID;
orderDTO.Point = person.Point.GetInt();
orderDTO.Telephone = person.Telephone.GetString();
}
if (order != null)
{
orderDTO.PersonID = order.PersonID;
orderDTO.Count = order.Count.GetInt();
orderDTO.Delivery = order.Delivery.GetDateTime();
orderDTO.Favorable = order.Favorable.GetDouble();
orderDTO.Freightage = order.Freightage.GetDouble();
orderDTO.OrderID = order.ID;
orderDTO.OrderNumber = order.OrderNumber.GetString();
orderDTO.Price = order.Price.GetDouble();
orderDTO.TotalPrice = order.TotalPrice.GetDouble();
var orderItemList = order.OrderItem.ToList();
if (orderItemList.Count != 0)
{
var orderItemDTO = new List<OrderItemDTO>();
foreach (var orderItem in orderItemList)
orderItemDTO.Add(GetOrderItemDTO(orderItem));
orderDTO.OrderItemList = orderItemDTO;
}
}
return orderDTO;
}
public static OrderItemDTO GetOrderItemDTO(OrderItem orderItem)
{
OrderItemDTO orderItemDTO = new OrderItemDTO();
orderItemDTO.Count = orderItem.Count.GetInt();
orderItemDTO.Goods = orderItem.Goods.GetString();
orderItemDTO.OrderID = orderItem.OrderID;
orderItemDTO.OrderItemID = orderItem.ID;
orderItemDTO.Price = orderItem.Price.GetDouble();
return orderItemDTO;
}
//把DTO轉換成多個對象
public static void SetOrder(OrderDTO orderDTO, out Person person, out Order order)
{
person = new Person();
person.EntityKey=new System.Data.EntityKey("BusinessContext.Person","ID",orderDTO.PersonID);
person.Address = orderDTO.Address;
person.EMail = orderDTO.EMail;
person.ID = orderDTO.PersonID;
person.Name = orderDTO.Name;
person.Point = orderDTO.Point;
person.Telephone = orderDTO.Telephone;
order = new Order();
order.EntityKey=new System.Data.EntityKey("BusinessContext.Order","ID",orderDTO.OrderID);
order.Count = orderDTO.Count;
if (orderDTO.Delivery.Year!=0001&&orderDTO.Delivery.Year!=9999)
order.Delivery = orderDTO.Delivery;
order.Favorable = orderDTO.Favorable;
order.Freightage = orderDTO.Freightage;
order.ID = orderDTO.OrderID;
order.OrderNumber = orderDTO.OrderNumber;
order.PersonID = orderDTO.PersonID;
order.Price = orderDTO.Price;
order.TotalPrice = orderDTO.TotalPrice;
var orderItemDTOList = orderDTO.OrderItemList;
if (orderItemDTOList.Count() != 0)
foreach (var orderItemDTO in orderItemDTOList)
order.OrderItem.Add(GetOrderItem(orderItemDTO));
}
public static OrderItem GetOrderItem(OrderItemDTO orderItemDTO)
{
OrderItem orderItem = new OrderItem();
orderItem.EntityKey = new System.Data.EntityKey("BusinessContext.OrderItem", "ID", orderItemDTO.OrderItemID);
orderItem.Count = orderItemDTO.Count;
orderItem.Goods = orderItemDTO.Goods;
orderItem.ID = orderItemDTO.OrderItemID;
orderItem.OrderID = orderItemDTO.OrderID;
orderItem.Price = orderItemDTO.Price;
return orderItem;
}
}
}
//數據傳輸對象 DTO
namespace Business.TransferObject
{
[DataContract]
public class OrderItemDTO
{
private int _orderItemID;
private int _orderID;
private string _goods;
private double _price;
private int _count;
[DataMember]
public int OrderItemID
{
get { return _orderItemID; }
set { _orderItemID = value; }
}
............
............
}
[DataContract]
public class OrderDTO
{
private int _personID;
private string _name;
private string _address;
private string _telephone;
private int _point;
private string _email;
private int _orderID;
private string _orderNumber;
private int _count;
private double _freightage;
private double _favorable;
private DateTime _delivery;
private double _price;
private double _totalPrice;
private IList<OrderItemDTO> _orderItemDTOList;
[DataMember]
public int PersonID
{
get{return this._personID;}
set{this._personID=value;}
}
..........
..........
}
}
通過數據轉換器,可以順利實現領域模型與DTO之間的轉換,協調應用層服務的運行。
4. 應用層的發布
在開發SOA系統的時候,應用層的服務需要使用遠程方法對外開放,在接收到請求的時候,它可以調用領域層服務獲取運算結果,然后通過數據轉換器OperationAssembler把運算結果轉換成DTO,最后返還到表現層。在起初,我曾嘗試對應每個應用層的對象建立一個遠程接口,但經過多次重構以后,我覺得行程對象就是一個簡單的對外接口,對象之間不存在什么邏輯關系。所以更簡單的方法是使用外觀模式,建立少數的幾個遠程服務類,把所有的應用層對象的方法都包含在內。
?
可以留意代碼,OperationService包括了對Person模型和Order模型的所有操作。而且每個操作都只是簡單地調用應用層服務 (ApplicationService) 獲得計算結果,然后使用數據轉換器 (OperationAssembler)轉換數據,當中并不存在任何的業務邏輯。
namespace Business.Service.ApplicationService{
[ServiceContract]
public interface IOperationService
{
[OperationContract]
int AddOrder(ref OrderDTO orderDTO);
[OperationContract]
int DeleteOrder(OrderDTO orderDTO);
[OperationContract]
int UpdateOrder(ref OrderDTO orderDTO);
[OperationContract]
IList<OrderDTO> GetOrderByPerson(int personID);
[OperationContract]
OrderDTO GetOrder(int orderID);
[OperationContract]
int AddPerson(ref OrderDTO orderDTO);
[OperationContract]
int UpdatePerson(ref OrderDTO orderDTO);
[OperationContract]
OrderDTO GetPerson(int personID);
[OperationContract]
IList<OrderDTO> GetPersonList();
[OperationContract]
OrderDTO Payment(int orderID);
}
public class OperationService:IOperationService
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public int AddOrder(ref OrderDTO orderDTO)
{
OrderService orderService = new OrderService();
Order order = GetOrder(orderDTO);
int n = orderService.AddOrder(order);
orderDTO = OperationAssembler.GetOrderDTO(order, null);
return n;
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public int DeleteOrder(OrderDTO orderDTO)
{
OrderService orderService = new OrderService();
return orderService.DeleteOrder(GetOrder(orderDTO));
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public int UpdateOrder(ref OrderDTO orderDTO)
{
OrderService orderService = new OrderService();
Order order = GetOrder(orderDTO);
int n = orderService.UpdateOrder(order);
orderDTO = OperationAssembler.GetOrderDTO(order, null);
return n;
}
..............
..............
}
}
回到目錄?
八、系統總體架構
1. 體現領域驅動設計的架構
到此總結一下領域驅動設計DDD的總體結構,Repository層使用ORM映射或SQL命令等方式把持久化數據轉化為領域對象,然后根據業務邏輯設計對應領域層服務Domain Service 。接著應用層進行操作上的協調,利用Repository、領域模型、領域層服務Domain Service 完成業務需要,再通過數據轉換器把領域對象Domain Object轉化為數據傳輸對象DTO。最后,利用遠程通訊技術把應用層的服務(Application Service)對外開放。
注意留意的是SOA系統中,UI表現層與Application Service應用層服務是實現分離的,表現層可以同時調用多方的遠程服務來完成工作。
?
?
2. 體現面向服務開發的架構
面向服務開發SOA的架構主要體現在表現層與應用層之間通過遠程通訊實現分離,表現層可以引用多方的應用服務作為基礎。由此系統實現業務上的分離,不同的功能模塊可以獨立開發,最后通過服務在表現層共同體現。長期的發展,使不少的企業針對單個功能模塊開發出一套獨立的系統,再通過強大的虛擬化技術為第三方提供服務,這就是云計算的前身。
就像一個通訊購物的平臺,其實就是綜合了內部業務管理、銀行轉帳服務、呼叫中心、第三方接口等多方服務的綜合性平臺。如果你有過這方面的經驗,就會知道其實銀行轉帳、呼叫中心不過就是銀行、電信、移動等公司提供的幾個簡單的接口。開發人員根本無需理會其實內部的結構,只要通過幾個簡單的遠程方法就能調用。這正是應用層服務 Application Service 的最好體現。
3. 結束語
寫這篇文章目的只是想與各位分享一下我在開發過程中的一些體會,歡迎各位點評,指出其中的不足。
其實架構是死物,人才是有腦子的生物。每一個架構必然會有其優點,也會有不足之處,我們應該從開發之中一齊起來體驗,而不是盲目地跟從,希望在下的拙見能夠給大家帶來幫助??蓜e忘了支持一下,挺一挺。
對 .NET 開發有興趣的朋友歡迎加入QQ群:230564952 共同探討 !
源代碼下載 (數據庫可以在.edmx文件根據模型生成)
回到目錄
?
相關文章
SOA的概念
SOA基本架構
結合領域驅動設計的SOA分布式軟件架構
作者:風塵浪子
http://www.cnblogs.com/leslies2/archive/2011/12/12/2272722.html
原創作品,轉載時請注明作者及出處
轉載于:https://www.cnblogs.com/leslies2/archive/2011/12/12/2272722.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的结合领域驱动设计的SOA分布式软件架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于页面之间传参时有空格,中文及点击页面
- 下一篇: Cheatsheet: 2011 12.