DDD的模型选择

关于DDD的模型选择,应该是在05年的时候,从充血模型转换到贫血模型,那时候的资料太少,自己是通过项目体会出来的,架构经过这些年的升级改进,从模型方面这一块基本应该是不再有大的变化了。至少这些年的这么多项目,用起来非常顺手,从分析、设计、编码一路映射下来,现在又加个工作流、静态图,也只是对框架的完善。

我说说自己的理解。

//---------------------------------------

说DDD,先上标准的图和解释:

      1. 用户界面/展现层 
  负责向用户展现信息以及解释用户命令。更细的方面来讲就是: 
  a) 请求应用层以获取用户所需要展现的数据; 
  b) 发送命令给应用层要求其执行某个用户命令。
 
  2. 应用层 
  很薄的一层,定义软件要完成的所有任务。对外为展现层提供各种应用功能(包括查询或命令),对内调用领域层(领域对象或领域服务)完成各种业务逻辑,应用层不包含业务逻辑。
 
  3. 领域层 
  负责表达业务概念,业务状态信息以及业务规则,领域模型处于这一层,是业务软件的核心。
 
  4. 基础设施层 
  本层为其他层提供通用的技术能力;提供了层间的通信;为领域层实现持久化机制;总之,基础设施层可以通过架构和框架来支持其他层的技术需求。

//--------------------------------------

1、界面层:就是获取显示数据,把数据发送到服务端,并告诉服务端这些数据干什么用。

2、应用层?我有些不理解:作用是什么呢?存在价值是什么?如果用的是充血模型,如果对客户端通信的话,可以在此解耦,以值类型实现远程通信,包括SOA的支持等。如果想走SOA,充血模型应该是行不通的,特别是如果用的ORM后,可能有的是动态类,序列化与反序列化,都是一个大问题。如果是贫血模型,这一层好像是多余的,或者也有是为了SOA或者其他之类的服务,或者技术难题,这是可以理解的。如果是作为流程编排层,那么与应用层的定义就有差异了。在我的框架里面,就不存在应用层,但是存在流程编排层,但不是作为独立的层次存在,从有篇随笔上已经有表现了。

3、领域层:我想可以理解为业务逻辑层。如果说一定要纯粹的OO,一个类一定要有属性、方法才是完整意义上的OO,那好像有些太本本主义了。重要是要好用,能够方便的实现问题的解决。   如果以Service对外公开服务也未偿不可,或者说方法,方法分几4种,1是public对客户端公开的粗粒度的服务,相当于SOA用到的服务;2是internal对DLL公开的内部可以访问的服务,对模型内有效;3是protected 对继承的对象有效;4是private只对内部有效。组合起来,应该能够实现领域层的要求,不足就是本来一个对象一个类可以实现的东东,硬生生把人分家。有人提到公开了增删改的操作,没有必要公开,多余了,其实不就是4个操作嘛,也差不多哪里去了。没有十全十美的方法,有缺点,也有优点,可以直接对客户端提供服务,统一模型。

4、基础设施层:可能更多的是ORM,持久化之类的吧。当然会有一些通用服务等等。

还是一句话,没有十全十美的。不同的理解会造就不同的模型,产生可能相当大的差异。

上一段代码,看看我的理解:

1、实体对象。  这是一个主从结构 ,从下面的代码上面已经能够明细看的出来了     

 Order
namespace eWMS.Data
{
    using System;
    using System.Collections.Generic;
    using EES.Common;
    using EES.Common.Data;
    using EES.Common.Model;
    using System.ComponentModel;

    
    
    [EESData("移库单")]
    [Contract()]
    public class MOVHeader : EESObject
    {
        
        #region 字段属性
        private String id;
        
        private String corpCode;
        
        private String code;
        
        private String moc;
        
        private String orderStatus;
        
            
        #endregion
        
        #region 映射属性
        /// <summary>
        /// Id
        /// </summary>
        public virtual String Id
        {
            get
            {
                return id;
            }
            set
            {
                if ((id != value))
                {
                    this.OnPropertyChanging("Id");
                    id = value;
                    this.OnPropertyChanged("Id");
                }
            }
        }
        
        /// <summary>
        /// 企业编码
        /// </summary>
        public virtual String CorpCode
        {
            get
            {
                return corpCode;
            }
            set
            {
                if ((corpCode != value))
                {
                    this.OnPropertyChanging("CorpCode");
                    corpCode = value;
                    this.OnPropertyChanged("CorpCode");
                }
            }
        }
        
        /// <summary>
        /// 单号
        /// </summary>
        [Key()]
        public virtual String Code
        {
            get
            {
                return code;
            }
            set
            {
                if ((code != value))
                {
                    this.OnPropertyChanging("Code");
                    code = value;
                    this.OnPropertyChanged("Code");
                }
            }
        }
        
        /// <summary>
        /// 纸面单据号
        /// </summary>
        public virtual String Moc
        {
            get
            {
                return moc;
            }
            set
            {
                if ((moc != value))
                {
                    this.OnPropertyChanging("Moc");
                    moc = value;
                    this.OnPropertyChanged("Moc");
                }
            }
        }
        
        /// <summary>
        /// 单据状态
        /// </summary>
        public virtual String OrderStatus
        {
            get
            {
                return orderStatus;
            }
            set
            {
                if ((orderStatus != value))
                {
                    this.OnPropertyChanging("OrderStatus");
                    orderStatus = value;
                    this.OnPropertyChanged("OrderStatus");
                }
            }
        }
      
        #endregion

        private DataCollection<MOVDetail> detailCollection;
        public virtual DataCollection<MOVDetail> DetailCollection
        {
            get { return detailCollection; }
            set { detailCollection = value; }
        }

        private DataCollection<MOVData> movDataCollection;
        public virtual DataCollection<MOVData> MOVDataCollection
        {
            get { return movDataCollection; }
            set { movDataCollection = value; }
        }
    }
}

 

DTL 
  1 namespace eWMS.Data
  2 {
  3     using System;
  4     using System.Collections.Generic;
  5     using EES.Common;
  6     using EES.Common.Data;
  7     using EES.Common.Model;
  8     using System.ComponentModel;
  9     
 10     
 11     [EESData("单明细")]
 12     [Contract()]
 13     public class MOVDetail : EESObject
 14     {
 15         
 16         #region 字段属性
 17         private String id;
 18         
 19         private String corpCode;
 20         
 21         private String ordCode;
 22         
 23         private Int32 rowNo;
 24         
 25         private String rowStatus;
 26         
 27        
 28         #endregion
 29         
 30         #region 映射属性
 31         /// <summary>
 32         /// Id
 33         /// </summary>
 34         [Key()]
 35         public virtual String Id
 36         {
 37             get
 38             {
 39                 return id;
 40             }
 41             set
 42             {
 43                 if ((id != value))
 44                 {
 45                     this.OnPropertyChanging("Id");
 46                     id = value;
 47                     this.OnPropertyChanged("Id");
 48                 }
 49             }
 50         }
 51         
 52         /// <summary>
 53         /// 公司编码
 54         /// </summary>
 55         public virtual String CorpCode
 56         {
 57             get
 58             {
 59                 return corpCode;
 60             }
 61             set
 62             {
 63                 if ((corpCode != value))
 64                 {
 65                     this.OnPropertyChanging("CorpCode");
 66                     corpCode = value;
 67                     this.OnPropertyChanged("CorpCode");
 68                 }
 69             }
 70         }
 71         
 72         /// <summary>
 73         /// 到货单号
 74         /// </summary>
 75         public virtual String OrdCode
 76         {
 77             get
 78             {
 79                 return ordCode;
 80             }
 81             set
 82             {
 83                 if ((ordCode != value))
 84                 {
 85                     this.OnPropertyChanging("OrdCode");
 86                     ordCode = value;
 87                     this.OnPropertyChanged("OrdCode");
 88                 }
 89             }
 90         }
 91         
 92         /// <summary>
 93         /// 行号
 94         /// </summary>
 95         public virtual Int32 RowNo
 96         {
 97             get
 98             {
 99                 return rowNo;
100             }
101             set
102             {
103                 if ((rowNo != value))
104                 {
105                     this.OnPropertyChanging("RowNo");
106                     rowNo = value;
107                     this.OnPropertyChanged("RowNo");
108                 }
109             }
110         }
111         
112         /// <summary>
113         /// 行状态
114         /// </summary>
115         public virtual String RowStatus
116         {
117             get
118             {
119                 return rowStatus;
120             }
121             set
122             {
123                 if ((rowStatus != value))
124                 {
125                     this.OnPropertyChanging("RowStatus");
126                     rowStatus = value;
127                     this.OnPropertyChanged("RowStatus");
128                 }
129             }
130         }
131         
132       
133         #endregion
134     }
135 }

 

DATA
namespace eWMS.Data
{
    using System;
    using System.Collections.Generic;
    using EES.Common;
    using EES.Common.Data;
    using EES.Common.Model;
    using System.ComponentModel;
    
    
    [EESData("编码")]
    [Contract()]
    public class MOVData : EESObject
    {
        
        #region 字段属性
        private String id;
        
        private String ordCode;
        
        private String detailId;
        
        private String productCode;
        
        private String productSKU;
        
        private String code;
        
       
        #endregion
        
        #region 映射属性
        /// <summary>
        /// Id
        /// </summary>
        [Key()]
        public virtual String Id
        {
            get
            {
                return id;
            }
            set
            {
                if ((id != value))
                {
                    this.OnPropertyChanging("Id");
                    id = value;
                    this.OnPropertyChanged("Id");
                }
            }
        }
        
        /// <summary>
        /// 单号
        /// </summary>
        public virtual String OrdCode
        {
            get
            {
                return ordCode;
            }
            set
            {
                if ((ordCode != value))
                {
                    this.OnPropertyChanging("OrdCode");
                    ordCode = value;
                    this.OnPropertyChanged("OrdCode");
                }
            }
        }
        
        /// <summary>
        /// 明细Id
        /// </summary>
        public virtual String DetailId
        {
            get
            {
                return detailId;
            }
            set
            {
                if ((detailId != value))
                {
                    this.OnPropertyChanging("DetailId");
                    detailId = value;
                    this.OnPropertyChanged("DetailId");
                }
            }
        }
        
        /// <summary>
        /// 产品编码
        /// </summary>
        public virtual String ProductCode
        {
            get
            {
                return productCode;
            }
            set
            {
                if ((productCode != value))
                {
                    this.OnPropertyChanging("ProductCode");
                    productCode = value;
                    this.OnPropertyChanged("ProductCode");
                }
            }
        }
        
        /// <summary>
        /// 产品SKU
        /// </summary>
        public virtual String ProductSKU
        {
            get
            {
                return productSKU;
            }
            set
            {
                if ((productSKU != value))
                {
                    this.OnPropertyChanging("ProductSKU");
                    productSKU = value;
                    this.OnPropertyChanged("ProductSKU");
                }
            }
        }
        
        /// <summary>
        /// 追踪码
        /// </summary>
        public virtual String Code
        {
            get
            {
                return code;
            }
            set
            {
                if ((code != value))
                {
                    this.OnPropertyChanging("Code");
                    code = value;
                    this.OnPropertyChanged("Code");
                }
            }
        }
      
        #endregion
    }
}

2、服务:或者说方法。

服务/方法
namespace eWMS.Service
{
    using System;
    using System.Collections.Generic;
    using EES.Common;
    using EES.Common.Data;
    using EES.Common.Model;
    using EES.Common.Query;
    using EES.Common.Injectors.Handlers;
    using eWMS.Data;
    using T.BC.Service;
    
    
    [EESBO("移库单服务")]
    public class MOVHeaderService : MOVHeaderImp<MOVHeader>
    {
        
        [Operation(ScopeOption.Required)]
    public virtual void WriteTrans(MOVHeader h)
        {
            trans.Id = GUID.NewGuid();
            trans.OrderCode = h.Code;
            trans.OrderType = typeof(MOVHeader).ToString();
            trans.ProductCode = detail.ProductCode;
            trans.ProductSKU = detail.ProductSKU;
            trans.BatchCode = detail.BatchCode;
            trans.StoreCode = detail.StoreCode;
            trans.StoreZone = detail.StoreZone;
            trans.Qty = 0;
            trans.UOM = detail.UOM;
            trans.RowNo = detail.RowNo;
            trans.PackCode = detail.PackCode;
            getProxyTrans().Save(trans);
        }


        protected override void OnSaved(MOVHeader t)
        {
            getProxyMovDetail().SaveAll(t.DetailCollection);
            getProxyMovData().SaveAll(t.MOVDataCollection);
            base.OnSaved(t);
        }


        private MOVDetailService proxyMovDetail;
        private MOVDetailService getProxyMovDetail()
        {
            if (proxyMovDetail == null)
                proxyMovDetail = Factory.getProxy<MOVDetailService>();
            return proxyMovDetail;
        }
        private MOVDataService proxyMovData;
        private MOVDataService getProxyMovData()
        {
            if (proxyMovData == null)
                proxyMovData = Factory.getProxy<MOVDataService>();
            return proxyMovData;
        }
        private OrderTransactionService proxyTrans;
        private OrderTransactionService getProxyTrans()
        {
            if (proxyTrans == null)
                proxyTrans = Factory.getProxy<OrderTransactionService>();
            return proxyTrans;
        }

    }
}

为什么叫方法或者服务呢,结合框架这些方法可以直接公开出来,可以远程访问的,不需要加额外的编码,另外流程引擎也可以直接调用这些服务的,然后流程引擎会组合成新的服务对客户端公开。对于事务的处理,有[Operation(ScopeOption.Required)]这一句就可以了,也可以通过配置文件加上去。异常可以通过服务直接抛到客户端,如果在事务内则整个参与事务的所有服务的事务全部回滚,可能会涉及到多个服务的嵌套调用以及递归调用,全部会回滚,所涉及到的数据库连接会自动释放掉,缓存清理掉。
3、客户端调用:已经到了界面显示层。对于Web客户端与Form客户端的调用一样,已经到了界面显示层了。

客户端调用
namespace eWMS.Forms
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Drawing;
    using System.Windows.Forms;
    using EES.Common;
    using EES.Common.Data;
    using EES.Controls.Win;
    using EES.Common.Query;
    using EES.Common.MVC;
    using eWMS.Data;
    using eWMS.Service;
    
    
    [View("移动类型管理", EntryType.入口, "移动类型", "移动类型管理")]
    public sealed partial class BillTypeLForm : EForm
    {
        
        private BillTypeService proxy;
        
        public BillTypeLForm()
        {
            this.InitializeComponent();
            // 绑定数据错误不提示
            this.gridView.DataError += GrivViewDataError;
            this.gridView.CellValidated += GrivViewCellValidated;
            // 数据源
            this.gridView.DataSource = this.bindingSource;
            // 
            this.bindingSource.AddingNew += AddingNew;
            this.Input = new DataCollection<BillType>();

            DataLoad();
        }
        
        private BillTypeService getProxy()
        {
            if(proxy==null)
                proxy=Factory.getProxy<BillTypeService>() ;
            return proxy;
        }
        
        private void AddingNew(object sender, System.ComponentModel.AddingNewEventArgs e)
        {
            BillType obj = Factory.Create<BillType>();
            // 更多初始化
            // 
            e.NewObject = obj;
        }
        
        /// <summary>
        /// 刷新
        /// </summary>
        [Func("刷新", Ordinal=10)]
        public void DataLoad()
        {
            this.Input = this.getProxy().FindAll();
        }

        [Func("删除",Ordinal=35)]
        public void DataDelete()
        {
            BillType data = this.Current as BillType;
            if (data == null)
                throw new EESException("请选择要删除的数据");
            if (MessageBox.Show("确定要删除数据吗?", "删除数据", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == System.Windows.Forms.DialogResult.OK)
            {
                if (data.DataState != DataState.Created)
                {
                    this.getProxy().Delete(data);
                }
                data.DirectRemove();
                MessageBox.Show("删除 成功");
            }
        }
        
        [Func("保存", Ordinal=30)]
        public void DataSave()
        {
            object data = this.Input;
            if (data is DataCollection<BillType>)
            {
                this.Input = this.getProxy().SaveAll(((DataCollection<BillType>)(data)));
                MessageBox.Show("保存 成功");
            }
        }

        /// <summary>
        /// 查询
        /// </summary>
        [Func("查询", Ordinal = 40)]
        public void DataFind()
        {
            BillTypeFForm form = new BillTypeFForm();
            form.OnOK += delegate(object sender, TEventArgs<DataCollection<BillType>> e){this.Input=e.Data;};
            form.ShowDialog();
        }

    }
}


还没有说完,后面再继续吧

有不到之处,请大家批评指正……

谢谢!

补充:

关于对象模型,是一个可以上下追溯的递归结构,支持级联触发绑定的。根对象可以通过关联关系访问下级对象,在我的对象模型里面,底层的对象也仍然可以访问到上级的父对象,从而访问其他的兄弟对象。级联触发,则可以实现最底层对象的属性或增删,会冒泡一路通知到上顶层对象。在远程传输的时候,仍然可以记录数据状态的变化,传输过程中丢弃变化之前的数据,但会传输已经删除了的记录,服务端再作进一步的处理,这些过程都是自动实现的。

 举个例子,就按上面客户端的调用来说。

触发
       [Func("保存", Ordinal=30)]
        public void DataSave()
        {
            object data = this.Input;
            if (data is DataCollection<BillType>)
            {
                this.Input = this.getProxy().SaveAll(((DataCollection<BillType>)(data)));
                MessageBox.Show("保存 成功");
            }
        }

当在界面上删除一条记录后,后台会自动记录已经删除了的记录,如果绑定的数据变化了,则会冒泡通知顶层对象的状态发生了变化,会由Unchanged->Modified。调用this.Input的时候,会把界面的数据写入内存里去,默认情况下是自动处理,也可以人工修改代码的逻辑。通过绑定处理,则会大大降低代码的差错率。

Proxy
       private BillTypeService proxy;

        private BillTypeService getProxy()
        {
            if(proxy==null)
                proxy=Factory.getProxy<BillTypeService>() ;
            return proxy;
        }

Proxy则是统一了服务端调用与客户端调用。当为客户端调用的时候,会自动加载客户端的配置文件,调用远程的服务;如果是服务端调用,则会创建ProxyClass,实现方法的调用。
[Func("保存", Ordinal=30)]

是为了实现基本菜单的配置,流程相关菜单通过流程引擎定义,在没有流程引擎之前,是通过这种方式分功能权限的。

客户端的代码量还是比较小的,UI的主要目的就是数据的显示与获取,交与服务端处理业务逻辑。当然人性化,友好就是另一回事了,对于模型来说没有影响。

 

对于DDD的领域工作单元,有点类似把服务或方法的组合起来。我的做法是一层对一层负责,比如:订单服务,对外的接口就是订单,因为所有明细的状态变化都会影响到订单抬头,提交的时候是以订单为单元的,当然,所以变化的信息也会一并提交;订单服务可以调用订单明细的服务,但不会调用订单明细分配数据的服务,订单明细会调用订单明细分配数据的服务,任务可以一层层分解,通常每一层的都不是非常复杂。中间服务调用的时候,类似于普通的调用,数据增删改查,可以一并处理,并且会保证事务的一致性,单数据库与多数据库统一处理。并且可以通过配置提供远程服务,并不需要额外的代码。

这是我的做法,不能说是对的,框架用了不少年了,只能说用起来挺顺手的,项目中测试的工作量非常小,很少会出现BUG,另外,需求信息的传递应该还是非常完整的,只要流程出来了,开发人员能够很快的实现代码。很多时候是分析人员的速度跟不上开发人员的速度。

 

 

 

posted on 2013-02-05 22:08  光影传说  阅读(5476)  评论(14编辑  收藏  举报