NLayerApp中,在領域模型層之上是應用程式層與分布式服務(Distributed Services)部分。應用程式層主要負責接收來自用戶端的請求資料,然後協調領域模型層與基礎結構層組件完成語義上相對獨立的任務;而分布式服務部分則為應用程式層與用戶端之間提供通訊的介面和技術架構,嚴格地說它已經不具備任何任務處理的責任了,在整個應用程式中是一個可有可無的角色:對於ASP.NET Web應用程式而言,它只需要引用應用程式層組件的介面,然後通過IoC獲得應用程式層組件實體即可,無需分布式服務的支援。當然,如果還需要考慮與其它系統的整合的話,那麼實現一個分布式服務還是很有必要的。今天我們先討論NLayerApp中的應用程式層。NLayerApp在應用程式層中將服務(Application Service)分為三種:Banking Management、Customers Management以及Sales Management。這可以從Application.MainModule.csproj項目中看出。在每種應用服務中,首先為該種服務定義了介面,比如IBankingManagementService等,然後使用相應的類實現了這些介面。從結構上看,還是比較簡單的,本文也不再對其中每個應用程式層服務的具體實現作過多介紹,但有幾個方面我還是打算再進一步討論一下。
構造器注入(Constructor Injection)
應用服務的實現,使用了構造器注入以獲得所需對象的執行個體。例如CustomerManagementService類的建構函式接收兩個參數:ICustomerRepository的執行個體,以及ICountryRepository的執行個體。當分布式服務元件使用IoCFactory.Instance.CurrentContainer.Resolve方法來獲得ICustomerManagementService的具體實現時,IoC容器會根據配置資訊來自動解析ICustomerRepository和ICountryRepository的依賴,從而在建立ICustomerManagementService對象的時候,將解析出來的repository實體傳給CustomerManagementService的建構函式。我們可以從Infrastructure.CrossCutting.IoC.csproj項目的IoCUnityContainer類的ConfigureRootContainer中找到這種依賴關係的設定代碼。有關NLayerApp中IoC容器的實現請參考《Microsoft NLayerApp案例理論與實踐 - 基礎結構層(Cross-Cutting部分)》。
//Register Repositories mappings// ...container.RegisterType<ICustomerRepository, CustomerRepository>(new TransientLifetimeManager());container.RegisterType<ICountryRepository, CountryRepository>(new TransientLifetimeManager());//Register application services mappings// ...container.RegisterType<ICustomerManagementService, CustomerManagementService>(new TransientLifetimeManager());
回過來再看CustomerManagementService類,它的建構函式需要ICustomerRepository和ICountryRepository兩個參數,這是因為CustomerManagementService類本身在實現上需要用到這些倉儲對象。事實上,ICustomerManagementService介面的實現並不規定實作類別必須接收這兩個參數。例如,假設我們因為測試的需要,設計了一個MockCustomerManagementService,它也實現了ICustomerManagementService介面,但由於是做測試,我們在這個Mock類中使用Dictionary、List等資料結構來類比repository的功能,於是在MockCustomerManagementService中,也就無需ICustomerRepository和ICountryRepository的執行個體了。比如我們的MockCustomerManagementService可以實現如下:
public class MockCustomerManagementService : ICustomerManagementService{ private readonly List<Customer> customerRepository = new List<Customer> customerRepository; public MockCustomerManagementService() { } public void AddCustomer(Customer customer) { if (!customerRepository.Contains(customer)) customerRepository.Add(customer); } // other method implementations...}
然後,在IoCUnityContainer中,將註冊ICustomerManagementService的代碼改為如下即可:
container.RegisterType<ICustomerManagementService, MockCustomerManagementService>(new TransientLifetimeManager());
資料轉送對象(DTO)
在NLayerApp中,使用領域實體(Domain Entities)作為資料轉送對象(DTO),同時也實現了一些用於特定用途的DTO,比如DistributedServices.MainModule.csproj項目裡的PagedCriteria。在應用服務上將領域實體作為資料轉送對象來處理,也就決定了在其更高層:分布式服務中,也必須使用領域實體作為DTO。原因很簡單:分布式服務並沒有將DTO轉換為領域實體的職責,這是應用程式層的任務。另一方面,原本WCF會在用戶端產生Contracts的代理類型的時候,會屏蔽掉領域實體作為DTO所帶來的弊端,但貌似NLayerApp的用戶端程式是直接引用的領域實體來進行資料交換的,從DDD的角度講,這種設計是有問題的。當然也應該具體情況具體分析。NLayerApp中,大多數View Model都能夠與領域實體的結構相對應,並且直接將領域實體用作DTO在一定程度上降低了開發複雜度,提高了生產率。NLayerApp在其官方的資料中也提到過這個問題:
The latter case is when we use DTOs (Data Transfer Objects) for remote communications between Tiers, where the domain model's internal entities would not flow to the presentation layer or any other point beyond the internal layers of the Service. DTO objects would be those provided to the presentation layer in a remote location.
If the implementation of the entities is strongly linked to a specific technology, it is contrary to the DDD Architecture recommendations because we are contaminating the entire architecture with a specific technology. However, we have the option of sending domain entities that are POCO (Plain Old CLR Objects), that is, serialized classes that are 100% custom code and do not depend on any data access technology. In this case, the approach can be good and very productive, because we could have tools that generate code for these entity classes for is.
Thus, this approach (Serialization of Domain entities themselves) has the disadvantage of leaving the service consumer directly linked to the domain entities, which could have a different life cycle than the presentation layer data model and even different changing rates. Therefore, this approach is suitable only when we maintain direct control over the whole application (including the client that consumes the web-service), like a typical N-Tier application. On the other hand, when implementing SOA services for unknown consumers it is usually a better option to use DTOs, as explained below.
NLayerApp使用的是“序列化的領域實體”(Serialized Domain Entities)這種方式。現在我們來瞭解一下幾個有關DTO的設計要點。
- DTO的設計需要面向用戶端(包括用戶端應用程式、與外部系統整合的Web Services等),用戶端的View Model需要什麼樣的資料,就設計什麼樣的DTO。應用程式層負責收發DTO資料,並根據DTO資料訪問領域模型中的實體,根據實體組裝DTO。ORM解決的是Domain Model與關係型資料庫之間的阻抗失衡,而DTO解決的是View Model與Domain Model之間的阻抗失衡
- DTO應該是POCO,它不能依賴於任何技術架構
- 對於中小型系統,可以考慮使用類似NLayerApp的Serialized Domain Entities方式,這可以提高開發效率;但如果是大型系統,還是建議使用DTO,有朋友會覺得每次根據View Model去設計DTO很耗時,但我覺得如果應用程式規模較大的時候,還是做足功夫比較好,磨刀不誤砍柴工,這樣在今後做系統整合的時候也會方便一些。可以考慮使用DSL與自動化代碼產生技術來解決DTO的設計問題
- WCF產生的代理類Data Contracts就是一種DTO,如果專用微軟的技術,那麼也就與上述第二點不矛盾,Serialized Domain Entities可以以Data Contracts的形式出現在用戶端程式中,一定程度上屏蔽了直接將Serialized Domain Entities用作DTO的負面影響
應用程式層服務對任務的協調職能
很多朋友無法理解應用程式層存在的意義,總覺得按照傳統的三層架構就是資料訪問層(DAL)、商務邏輯層(BLL)和表現層(Presentation)。NLayerApp的系統架構為我們展現了應用程式層的任務協調職能及其存在的必要性。例如BankingManagementService的PerformTransfer方法中,包含了位於基礎結構層的分散式交易處理和位於Domain Model層的repository與UoW的操作。而整個PerformTransfer方法則將這些操作整合起來,以完成一個特定的應用任務:完成轉賬的功能。通常情況下,應用程式層的代碼中會包含對其下各層組件的訪問,因此,DDD的分層並不是嚴格型的(上層僅能依賴於其直接下層)。當然,如果你的應用程式並不存在需要多層協調才能完成特定任務的情況的話,應用程式層也可以省略。
OK,今天就先討論到這裡,下一講我將簡要介紹一下NLayerApp中的分布式服務(Distributed Services)部分。