EaBIM一直以来积极响应国家“十二五”推进建筑业信息化的号召,对建筑领域的信息技术开展深入技术交流和探讨!致力于打造“BIM-建筑师-生态技术”三位一体综合资源交流共享平台,希望为BIM与可持续设计理念及技术的普及做出微小的贡献!!!

EaBIM

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
查看: 2241|回复: 32
打印 上一主题 下一主题

[架构模式] 结合领域驱动设计的SOA分布式软件架

[复制链接]

1514

主题

7465

帖子

1万

积分

admin

Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10

积分
12404

社区QQ达人

跳转到指定楼层
楼主
发表于 2014-1-10 10:30:47 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
引言
本文主要是参考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#综合揭秘——分部类和分部方法

[csharp] view plaincopy


  • 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的职能清晰按照领域模型划分。

[csharp] view plaincopy


  • 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数量不多的时候,这是一种十分有效的方法。

[csharp] view plaincopy


  • 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来完成

[csharp] view plaincopy


  • 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服务类进行管理。

[csharp] view plaincopy


  • 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等领域层服务来完成结账、付款等一系列复杂业务逻辑的。

[csharp] view plaincopy


  • 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对象可以适用于多个外观层,以减少数据转换所带来的麻烦。

[csharp] view plaincopy


  • 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)转换数据,当中并不存在任何的业务逻辑。

[csharp] view plaincopy


  • 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 的最好体现。



                               
登录/注册后可看大图

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 转播转播 分享分享 分享淘帖 支持支持 反对反对
工作时间:工作日的9:00-12:00/13:30-18:00,节假日不在线,请勿留言

11

主题

869

帖子

1482

积分

BIM经理

Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

积分
1482
5F
发表于 2014-4-10 16:11:18 | 只看该作者
(*^__^*) 嘻嘻……

0

主题

836

帖子

1198

积分

BIM项目负责人

Rank: 5Rank: 5Rank: 5Rank: 5Rank: 5

积分
1198
7F
发表于 2014-5-19 14:35:12 | 只看该作者
路过!!!
不发表意见……

19

主题

878

帖子

1359

积分

BIM经理

Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

积分
1359
11F
发表于 2014-6-19 10:59:24 | 只看该作者
顶......
楼下跟上.....

4

主题

851

帖子

1307

积分

BIM经理

Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

积分
1307
12F
发表于 2014-6-19 11:05:15 | 只看该作者
(*^__^*) 嘻嘻……

1514

主题

7465

帖子

1万

积分

admin

Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10Rank: 10

积分
12404

社区QQ达人

15F
 楼主| 发表于 2014-12-21 12:47:06 | 只看该作者
谢谢谢分享
工作时间:工作日的9:00-12:00/13:30-18:00,节假日不在线,请勿留言
*滑块验证:
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|EaBIM网 ( 苏ICP备2020058923号-1  苏公网安备32011502011255号

GMT+8, 2024-11-23 10:27

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表