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,另外,需求信息的傳遞應該還是非常完整的,只要流程出來了,開發人員能夠很快的實現代碼。很多時候是分析人員的速度跟不上開發人員的速度。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM