結合領域驅動設計的SOA分佈式軟件架構

  引言html

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

  最後兩節  細說應用層 、系統整體架構 是本文的重點,着重說明領域驅動設計與SOA之間的關係,對DDD有必定基礎的朋友能夠越過前面的幾節,直接查看第7、八節。網絡

  源代碼下載 (數據庫能夠在.edmx文件根據模型生成)架構

  1、SOA與DDD的定義分佈式

  SOA與DDD都是經常使用的系統架構,但二者之間所針對的核心是不一樣的。函數

  SOA(面向服務架構)由Gartner在1996年提出來,它是一種分佈式的軟件架構,它能夠根據需求經過網絡對鬆散耦合的粗粒度應用組件進行部署、組合和使用。簡單來講,SOA就是一種大型系統開發的體系架構,在基於SOA架構的系統中,具體應用程序的功能是由一些鬆耦合而且具備統一接口的組件(也就是service)組合構建起來的,它是針對多核心多平臺之間的數據交換。工具

  DDD(領域驅動設計)由Eric Evans在2004提出,它的核心內容是「如何將業務領域概念映射到軟件工程當中」。它推翻了「軟件從數據層開發設計」的舊習慣,強調領域模型在軟件中發揮的強大力量,注重如何把企業內部複雜的業務流程轉化爲軟件。性能

  也許能夠認爲SOA針對的是大型系統的整體架構,注重如何把系統進行項目分離,隔離開發,最後實現系統合併。而DDD是針對單個項目的開發管理過程,注重如何利用領域模型把業務需求轉化爲軟件。二者之間並無存在理論上的衝突,能把二者結合,各展所長,更能發揮各自的優點。網站

  2、DDD的分層結構ui

  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是若是利用領域模型去適應多變的業務邏輯關係。

  3、把業務關係轉化爲領域模型

  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; }
}
}
}

  4、細說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);
}
}
}

  5、領域層的服務

  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)
{
//肯定下單,創建訂單號
OrderManager orderManager = new OrderManager();
order.OrderNumber = Guid.NewGuid().ToString();
order.Delivery = DateTime.Now;
orderManager.UpdateOrder(order);
//爲用戶增長會員分數
PersonManager personManager = new PersonManager();
personManager.AddPoint(order.PersonID, (int)order.TotalPrice);
}
}
}

  利用領域層的服務,使得每一個Manager服務類的職能很是明確,業務管理起來也十分地方便,領域層能夠隨着業務的改變而靈活變更。最後加上實體基本的插入、更新、獲取等等操做,領域層的服務就能夠清晰地完成。

  能夠注意到每次加入新Order,或者更新Order的時候,都會調用到AccountManager對象來從新計算費用

namespace Business.Service.DomainService
{
public class OrderManager
{
private IOrderRepository orderRepository = DataAccess.CreateOrderRepository();

public int AddOrder(Order order)
{
//計算Order整體費用
Account(order);
//加入修改後的Order
return orderRepository.AddOrder(order);
}

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;
}

//計算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();
}

public int DeleteOrder(Order order)
{
return orderRepository.DeleteOrder(order.ID);
}
..................
..................
}

public class PersonManager
{
private IPersonRepository personRepository = DataAccess.CreatePersonRepository();

public int AddPerson(Person person)
{
return personRepository.AddPerson(person);
}

//增長分數
public int AddPoint(int personID,int point)
{
Person person = personRepository.GetPerson(personID);
person.Point=person.Point.GetInt()+point;
return personRepository.UpdatePerson(person);
}
...............
...............
}
}

  6、工廠模式Factory

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

  並不是生成全部對象的時候,都須要用到工廠模式。在生成簡單對象的時候,能夠直接利用構造函數來代替工廠,也能夠添加工廠方法來生成對象。但若是在生成對象時,內部屬性之間存在一系統複雜的業務規則的時候,就能夠把生成方法獨立到一個Factory類裏面。這時候客戶端無需理會潛在邏輯關係,而直接經過這個Factory來生成相應的對象。

  舉個例子,在新建Order的時候,業務上規定運費是整體金額的1%,折扣規定是7.5折...... 。若是由客戶端新建一個對象Order,而後爲這些屬性賦值,那相關的業務邏輯就會暴露在外。這時候就可使用Factory模式,把屬性之間的關係封裝到Factory以內,客戶端經過Factory就能輕鬆地生成Order對象而無須要理會複雜的內部關係。

  至於較複雜的Factory模式,在此很少做介紹,各位能夠在網上查找相關資料。

  7、細說應用層

  1. 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系統內,應該從性能出發優先考慮粗粒度元素的傳輸性問題)

  • 封裝性

  應用層並不須要複雜的模型,只需把一些功能封裝在少數的幾個應用層服務類裏面,使用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之間的轉換是一件複雜的事件,所以能夠創建一個數據轉換器實現此功能。

  在日常的工做裏,不太多會把「訂單管理系統」作成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;}
}
..........
..........
}
}

  4. 應用層的發佈

  在開發SOA系統的時候,應用層的服務須要使用遠程方法對外開放,在接收到請求的時候,它能夠調用領域層服務獲取運算結果,而後經過數據轉換器OperationAssembler把運算結果轉換成DTO,最後返還到表現層。在起初,我曾嘗試對應每一個應用層的對象創建一個遠程接口,但通過屢次重構之後,我以爲行程對象就是一個簡單的對外接口,對象之間不存在什麼邏輯關係。因此更簡單的方法是使用外觀模式,創建少數的幾個遠程服務類,把全部的應用層對象的方法都包含在內。

  能夠留意代碼,OperationService包括了對Person模型和Order模型的全部操做。並且每一個操做都只是簡單地調用領域層服務 (DomainService) 得到計算結果,而後使用數據轉換器 (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();
}
}

namespace Business.Service.ApplicationService
{
public class OperationService:IOperationService
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public int AddOrder(ref OrderDTO orderDTO)
{
OrderManager orderManager = new OrderManager();
Order order = GetOrder(orderDTO);
int n = orderManager.AddOrder(order);
orderDTO = OperationAssembler.GetOrderDTO(order, null);
return n;
}

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public int DeleteOrder(OrderDTO orderDTO)
{
OrderManager orderManager = new OrderManager();
return orderManager.DeleteOrder(GetOrder(orderDTO));
}
....................
....................
}
}

  8、系統整體架構

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

  到此總結一下領域驅動設計DDD的整體結構,Repository先使用ORM映射或SQL命令等方式把持久化數據轉化爲領域對象,而後根據業務邏輯把動做轉化爲領域層的服務(Domain Service),操做過程當中所針對的都是領域對象Domain Object 。接着,應用層經過數據轉換器把領域對象Domain Object轉化爲數據傳輸對象DTO,再利用應用層的服務(Application Service)開通對外的接口,最後表現層經過遠程調用獲取數據。

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

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

  面向服務開發SOA的架構主要體如今表現層與應用層之間經過遠程通信實現分離,表現層能夠引用多方的應用服務做爲基礎。由此係統實現業務上的分離,不一樣的功能模塊能夠獨立開發,最後經過服務在表現層共同體現。長期的發展,使很多的企業針對單個功能模塊開發出一套獨立的系統,再經過強大的虛擬化技術爲第三方提供服務,這就是雲計算的前身。

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

  3. 結束語

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

  其實架構是死物,人才是有腦子的生物。每個架構必然會有其優勢,也會有不足之處,咱們應該從開發之中一齊起來體驗,而不是盲目地跟從,但願在下的拙見可以給你們帶來幫助。可別忘了支持一下,挺一挺。

  源代碼下載 (數據庫能夠在.edmx文件根據模型生成)

  相關文章

  SOA的概念

  SOA基本架構  

  做者:風塵浪子

相關文章
相關標籤/搜索