關於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、實體對象。 這是一個主從結構 ,從下面的代碼上面已經能夠明細看的出來了

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

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 }

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的時候,會把界面的數據寫入內存里去,默認情況下是自動處理,也可以人工修改代碼的邏輯。通過綁定處理,則會大大降低代碼的差錯率。

private BillTypeService proxy; private BillTypeService getProxy() { if(proxy==null) proxy=Factory.getProxy<BillTypeService>() ; return proxy; }
Proxy則是統一了服務端調用與客戶端調用。當為客戶端調用的時候,會自動加載客戶端的配置文件,調用遠程的服務;如果是服務端調用,則會創建ProxyClass,實現方法的調用。
[Func("保存", Ordinal=30)]
是為了實現基本菜單的配置,流程相關菜單通過流程引擎定義,在沒有流程引擎之前,是通過這種方式分功能權限的。
客戶端的代碼量還是比較小的,UI的主要目的就是數據的顯示與獲取,交與服務端處理業務邏輯。當然人性化,友好就是另一回事了,對於模型來說沒有影響。
對於DDD的領域工作單元,有點類似把服務或方法的組合起來。我的做法是一層對一層負責,比如:訂單服務,對外的接口就是訂單,因為所有明細的狀態變化都會影響到訂單抬頭,提交的時候是以訂單為單元的,當然,所以變化的信息也會一並提交;訂單服務可以調用訂單明細的服務,但不會調用訂單明細分配數據的服務,訂單明細會調用訂單明細分配數據的服務,任務可以一層層分解,通常每一層的都不是非常復雜。中間服務調用的時候,類似於普通的調用,數據增刪改查,可以一並處理,並且會保證事務的一致性,單數據庫與多數據庫統一處理。並且可以通過配置提供遠程服務,並不需要額外的代碼。
這是我的做法,不能說是對的,框架用了不少年了,只能說用起來挺順手的,項目中測試的工作量非常小,很少會出現BUG,另外,需求信息的傳遞應該還是非常完整的,只要流程出來了,開發人員能夠很快的實現代碼。很多時候是分析人員的速度跟不上開發人員的速度。