结合领域驱动设计的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服务会大多置于应用层中,下面将会作进一步解释)

0?wx_fmt=gif

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 的定义来开发服务,在后面的开发过程中大家也可以从中体现一下服务层所带来的好处。

0?wx_fmt=jpeg

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);
        IListGetList();
        IListGetListByPerson(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);
        IListGetList();
    }
}

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

        public Person GetPerson(int id)
        {
            return LinqHelp.Get(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
        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服务类的职能非常明确,业务管理起来也十分地方便,领域层可以随着业务的改变而灵活变动。而且领域层具有 “高内聚,低耦合” 特性,它并不依赖其它任何一层,而只是把业务逻辑包含在里面。


出处:http://www.uml.org.cn/zjjs/201504301.asp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值