Microsoft NLayerApp案例理论与实践 - 应用层
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)部分。