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

作者: 風塵浪子  來源: 博客園  發布時間: 2012-01-22 21:00  閱讀: 4565 次  推薦: 2   原文鏈接   [收藏]  

  引言

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

  最后兩節  細說應用層系統總體架構 是本文的重點,著重說明領域驅動設計與SOA之間的關系,對DDD有一定基礎的朋友可以越過前面的幾節,直接查看第七、八節。

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

  一、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)
        {
            //確定下單,建立訂單號
            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);
        }
        ...............
        ...............
    }
}

  六、工廠模式Factory

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

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

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

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

  七、細說應用層

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

  八、系統總體架構

  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基本架構  

  作者:風塵浪子

2
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()