前言:前面三篇分享了下DDD里面的兩個主要特性:聚合和倉儲。領域層的搭建基本完成,當然還涉及到領域事件和領域服務的部分,后面再項目搭建的過程中慢慢引入,博主的思路是先將整個架構走通,然后一步一步來添加相關元素,使架構慢慢變得豐滿。這篇打算分享下應用層的搭建。根據DDD的設計原則,應用層不包含任何領域邏輯,它主要的作用是協調任務,或者叫調度任務,維護應用程序狀態。根據博主的理解,應用層是用來隔離領域層的,假設沒有應用層,那么我們的界面層可以直接調用領域層的邏輯,也就是說可以直接訪問領域的model,這樣的壞處顯而易見:一是領域model不是純粹的數據model,它含有領域的行為,直接將其傳到前台會造成調用的混亂;二是倉儲是和數據持久化打交道了,界面直接調用倉儲,也就是界面直接和數據打交道,也不符合一般分層的原則。所以我們引入應用層,本文應用層是一個以控制台項目為宿主的WCF服務。我們來看代碼設計。
DDD領域驅動設計初探系列文章:
- C#進階系列——DDD領域驅動設計初探(一):聚合
- C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)
- C#進階系列——DDD領域驅動設計初探(三):倉儲Repository(下)
- C#進階系列——DDD領域驅動設計初探(四):WCF搭建
- C#進階系列——DDD領域驅動設計初探(五):AutoMapper使用
- C#進階系列——DDD領域驅動設計初探(六):領域服務
- C#進階系列——DDD領域驅動設計初探(七):Web層的搭建
一、WCF簡介
WCF(Windows Communication Foundation)是由微軟發展的一組數據通信的應用程序開發接口,可以翻譯為Windows通訊接口,它是.NET框架的一部分。由 .NET Framework 3.0 開始引入。WCF的最終目標是通過進程或不同的系統、通過本地網絡或是通過Internet收發客戶和服務之間的消息。關於WCF的理論知識,需要我們了解的是經典的ABC。
- Address: 每一個WCF的Service都有一個唯一的地址。這個地址給出了Service的地址和傳輸協議(Transport Protocol)。
- Binding:綁定制定了服務通過什么形式訪問。只要類比傳輸協議, encoding (text, binary, etc) 以及 WS-* 協議,像transactional支持以及可信任的消息隊列。
- Contract:Contract描述了Service能提供的各種服務。Contract有四種,包括Service Contract, Data Contract, Fault Contract和Message Contract。
關於WCF的理論在此就不再展開,下面結合我們的項目代碼我們從零開始一步一步來搭建一個自己的WCF服務吧。
二、WCF代碼示例
1、代碼結構圖

項目按照模塊為單位划分服務,比如權限模塊,我們就有一個權限的接口契約IPowerManageWCFService。IService文件夾里面放了3個接口,分別對應系統3個模塊的接口契約,Service文件夾里面分別對應了3個接口的實現。ServiceAttribute.cs里面定義了兩個特性,表示接口是WCF的服務。我們來看看具體的代碼。
2、代碼示例
2.1 ServiceAttribute.cs文件定義契約接口和實現的特性類:
namespace ESTM.WCF.Service { //標記此特性的為WCF服務接口 public class ServiceInterfaceAttribute : Attribute { } //標記此特性的為WCF服務接口實現類 public class ServiceClassAttribute : Attribute { } }
2.2 接口契約代碼:
/// <summary> /// 工廠布局模塊接口契約 /// </summary> [ServiceInterface] [ServiceContract] public interface IFactoryLayoutWCFService { [OperationContract] List<DTO_TM_PLANT> GetAllPlant(); }
/// <summary> /// 權限管理模塊接口契約 /// </summary> [ServiceContract] [ServiceInterface] public interface IPowerManageWCFService { [OperationContract] IList<DTO_TB_DEPARTMENT> GetAllDepartment(); }
/// <summary> /// 產品管理模塊接口契約 /// </summary> [ServiceContract] [ServiceInterface] public interface IProductWCFService { [OperationContract] IList<DTO_TP_PRODUCT> GetAllProduct(); }
接口契約[ServiceContract]表示該接口遵守接口契約協定,[OperationContract]操作契約,這兩個特性都是WCF內置的東西。[ServiceInterface]的用處我們待會說。
2.3 接口實現代碼
[ServiceClass] public class FactoryLayoutWCFService : IFactoryLayoutWCFService { public List<DTO_TM_PLANT> GetAllPlant() { throw new NotImplementedException(); } }
[ServiceClass] public class ProductWCFService : IProductWCFService { public IList<DTO_TP_PRODUCT> GetAllProduct() { throw new NotImplementedException(); } }
[ServiceClass] public class PowerManageWCFService : IPowerManageWCFService { public IList<DTO_TB_DEPARTMENT> GetAllDepartment() { throw new NotImplementedException(); } }
[ServiceClass]特性和接口上面的[ServiceInterface]特性對應,用於標記契約和實現。
2.4 Bootstrapper.cs里面定義了服務的啟動方法
public class Bootstrapper { private string strBaseServiceUrl = ConfigurationManager.AppSettings["ServiceUrl"].ToString(); //啟動所有的服務 public void StartServices() { //1.讀取此程序集里面的有服務契約的接口和實現類 var assembly = Assembly.Load(typeof(Bootstrapper).Namespace); var lstType = assembly.GetTypes(); var lstTypeInterface = new List<Type>(); var lstTypeClass = new List<Type>(); foreach (var oType in lstType) { //2.通過接口上的特性取到需要的接口和實現類 var lstCustomAttr = oType.CustomAttributes; if (lstCustomAttr.Count() <= 0) { continue; } var oInterfaceServiceAttribute = lstCustomAttr.FirstOrDefault(x => x.AttributeType.Equals(typeof(ServiceInterfaceAttribute))); if (oInterfaceServiceAttribute != null) { lstTypeInterface.Add(oType); continue; } var oClassServiceAttribute = lstCustomAttr.FirstOrDefault(x => x.AttributeType.Equals(typeof(ServiceClassAttribute))); if (oClassServiceAttribute != null) { lstTypeClass.Add(oType); } } //3.啟動所有服務 foreach (var oInterfaceType in lstTypeInterface) {
//通過反射找到接口的實現類,找到配對然后啟動服務 var lstTypeClassTmp = lstTypeClass.Where(x => x.GetInterface(oInterfaceType.Name) != null).ToList(); if (lstTypeClassTmp.Count <= 0) { continue; } if(lstTypeClassTmp[0].GetInterface(oInterfaceType.Name).Equals(oInterfaceType)) { var oTask = Task.Factory.StartNew(() => { OpenService(strBaseServiceUrl + "/" + oInterfaceType.Name, oInterfaceType, lstTypeClassTmp[0]); }); } } } //通過服務接口類型和實現類型啟動WCF服務 private void OpenService(string strServiceUrl, Type typeInterface, Type typeclass) { Uri httpAddress = new Uri(strServiceUrl); using (ServiceHost host = new ServiceHost(typeclass)) { ///////////////////////////////////////添加服務節點/////////////////////////////////////////////////// host.AddServiceEndpoint(typeInterface, new WSHttpBinding(), httpAddress); if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) { ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; behavior.HttpGetUrl = httpAddress; host.Description.Behaviors.Add(behavior); } host.Opened += delegate { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("服務啟動成功。服務地址:" + strServiceUrl); }; host.Open(); while (true) { Console.ReadLine(); } } } }
對應的App.Config里面對應的服務的URL
<appSettings> <add key="ServiceUrl" value="http://127.0.0.1:1234/MyWCF.Server"/> </appSettings>
StartServices()方法通過反射和兩個特性[ServiceClass]與[ServiceInterface],依次啟動三個服務。
然后再Program里面調用
static void Main(string[] args) { var oBootstrapper = new Bootstrapper(); oBootstrapper.StartServices(); Console.ReadLine(); }
得到結果:

我們隨便選擇一個服務,通過瀏覽器訪問,測試服務是否啟動成功。

至此,WCF服務基本完成。
三、DTO說明
DTO,全稱Data Transfer Object,數據傳輸對象。DTO是一個貧血模型,也就是它里面基本沒有方法,只有一堆屬性,並且所有屬性都具有public的getter和setter訪問器。為什么需要一個DTO對象?這個問題在C#進階系列——MEF實現設計上的“松耦合”(終結篇:面向接口編程)這篇里面介紹過,它的作用其實很單一,就是用於數據傳遞和數據綁定。至於DTO如何設計,博主的項目里,DTO是按照聚合來划分的,也就是一個聚合對應一個DTO,DTO里面屬性的定義可以根據項目需求來定。我們來看看代碼:

/// <summary> /// 所有DTO model的父類,用作泛型約束 /// </summary> [DataContract] public class DTO_BASEMODEL { }
/// <summary> /// TB_DEPARTMENT /// </summary> [DataContract] public class DTO_TB_DEPARTMENT : DTO_BASEMODEL { [DataMember] public string DEPARTMENT_ID { get; set; } [DataMember] public string DEPARTMENT_NAME { get; set; } [DataMember] public string PARENT_ID { get; set; } [DataMember] public string DEPARTMENT_LEVEL { get; set; } [DataMember] public string STATUS { get; set; } }
其他DTO都和這個類似,就不一一列舉了。由於DTO需要由WCF傳遞到Web前台,所以要求這個對象可以序列化,需要標記[DataContract]和[DataMember]兩個特性,DTO_BASEMODEL作為所有DTO的父類, 用作泛型約束和定義DTO的一些公用特性。到此,WCF的搭建基本完成,下篇我們來介紹下Automapper的使用。累死我了,今天先到這吧,也不早了,博主也要安歇了。晚安!
還是附上 源碼 吧,要不然園友們要說博主小氣呢~~有興趣可以看看。
