zoukankan      html  css  js  c++  java
  • 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,另外,需求信息的传递应该还是非常完整的,只要流程出来了,开发人员能够很快的实现代码。很多时候是分析人员的速度跟不上开发人员的速度。

  • 相关阅读:
    React 之使用 fetch
    react-native 环境搭建
    create-react-app 配置 less
    React新的前端思维方式
    字体图标 —— IconMoon
    你不知道的javascript 之 >>
    前端的自我修养
    jquery 学习
    html的meta总结
    git基本操作 nginx基本操作
  • 原文地址:https://www.cnblogs.com/dreamstec/p/2893445.html
Copyright © 2011-2022 走看看