从这篇文章开始,我将逐步介绍NLayerApp的基础结构层、领域层、应用层以及分布式服务层。本文着重介绍基础结构层,根据上文对NLayerApp的架构分析,它将包含两大部分的内容:处理数据访问的基础结构层组件和Cross-Cutting的基础结构层组件。处理数据访问的基础结构层组件主要包含了仓储的具体实现、Unit Of Work(PoEAA,Martin Fowler)的实现、NLayerApp的实体模型定义,以及为单体测试做准备的Service Stubs(PoEAA,Martin Fowler);Cross-Cutting的基础结构层组件则主要包含了IoC(Inversion of Control)容器以及跟踪应用程序执行过程的Trace工具。虽然这些都是基础结构层的组件,但也包含了很多技术细节甚至是设计要点,就让我们一起对这些内容做一个详细的解读。
在应用程序设计的过程中,我们会基于这样一个设计准则,就是类型之间的关联应该依赖于接口或者抽象,而非具体的实现。这样就使得我们能够在保证整个程序结构不变的情况下,很方便地替换组件的具体实现方式,这不仅使得Service Stub模式的应用成为可能,从而提高了系统的可测试性,而且解耦了组件之间的依赖关系,降低了应用程序的维护成本。IoC容器是这样一种对象,它在应用程序的执行环境中维护着接口与其实现之间的映射关系,以及各个实现对象之间的依赖关系,以便当客户程序向IoC容器提出请求时,能够返回与所请求的接口或抽象类型所对应的具体实现,客户程序不需要去关心返回的具体实现究竟是什么,以及如何去初始化这个具体实现。本文不会对IoC作过多的介绍,有兴趣的朋友可以阅读《Inversion of Control Containers and the Dependency Injection pattern》这篇文章。
NLayerApp中IoC容器的实现依赖于Microsoft Patterns&Practices Unity,其实大多数应用程序甚至是开发框架都会依赖于第三方的类库来实现IoC容器,因为IoC本身涉及的内容就比较多,很好地解决类型之间复杂的依赖关系也不是一件很容易的事情。Unity并非IoC的唯一选择,除了Unity之外,Spring.NET、Castle Windsor、Ninject、StructureMap等都可以成为IoC容器不错的选择。NLayerApp中与IoC容器实现有关的类及其之间的关系如下图所示:
在上图中,IContainer接口定义了IoC容器相关的方法,它是与具体的实现技术无关的接口(接口的层次结构树、其中定义的方法的参数以及返回值等都不会依赖于任何第三方的组件),因此,理论上我们可以通过继承IContainer接口然后用我们自己的技术方式来实现IoC容器。NLayerApp是使用Unity作为IoC容器的,因此,上图的IoCUnityContainer类实现了IContainer接口,然后在IoCFactory的单件实例中通过new关键字创建了IoCUnityContainer的实例:
#region Constructor /// <summary> /// Only for singleton pattern, remove before field init IL anotation /// </summary> static IoCFactory() { } IoCFactory() { _CurrentContainer = new IoCUnityContainer(); } #endregion
当然,对于NLayerApp这一特定的应用程序案例而言,这样做是没什么问题的,但如果我们目前设计的是一个开发框架的话,直接使用new关键字来创建IoCUnityContainer的实例,就会使得IoCFactory强行依赖于IoCUnityContainer类型,于是也就违背了“关联应该依赖于接口或者抽象,而非具体实现”的设计准则。在最新版的Apworks框架的代码中,开发人员可以通过应用程序的配置信息来选择合适的IoC容器,比如你可以在应用程序启动的时候就决定是使用Unity还是Castle Windsor,这就使得框架本身具有更好的扩展性。刚才我们也讨论过,如果要使NLayerApp能够使用我们自定义的IoC容器,就要继承IContainer接口,那么现在我们还需要修改IoCFactory的私有构造函数,以使用我们自己的IoC容器来初始化_CurrentContainer私有成员。
Unity容器有一个非常实用的特点,就是“根容器”与“子容器”的概念,在“根容器”上通过调用CreateChildContainer方法即可创建与之关联的子容器。根容器和子容器都可以接受抽象类型的注册。每当客户程序向子容器请求类型(Resolve Type)时,Unity首先检查子容器中是否有所请求的类型,如果有,则直接返回该类型的具体实现,如果没有,则会将该请求转发给其父容器。利用Unity的这种特性,我们可以将针对不同部署环境的IoC容器进行统一管理,比如将各种部署环境中相同的类型映射注册在根容器中,然后为每个部署环境创建一个子容器,将与部署环境相关的特定类型映射注册在各自的子容器中。下图展示了NLayerApp中Unity IoC容器的基本情况:
通过阅读IoCUnityContainer的源代码我们可以了解到,在IoCUnityContainer的构造函数中,创建了rootContainer,并在rootContainer上使用CreateChildContainer创建了用于真实运行环境的realAppContainer以及用于单体测试的fakeAppContainer,之后就是使用下面的私有方法逐个初始化这些容器:
/// <summary> /// Configure root container.Register types and life time managers for unity builder process /// </summary> /// <param name="container">Container to configure</param> void ConfigureRootContainer(IUnityContainer container) { // Omitted... Please refer to the source code for details. } /// <summary> /// Configure real container. Register types and life time managers for unity builder process /// </summary> /// <param name="container">Container to configure</param> void ConfigureRealContainer(IUnityContainer container) { container.RegisterType<IMainModuleUnitOfWork, MainModuleUnitOfWork>(new PerExecutionContextLifetimeManager(), new InjectionConstructor()); } /// <summary> /// Configure fake container.Register types and life time managers for unity builder process /// </summary> /// <param name="container">Container to configure</param> void ConfigureFakeContainer(IUnityContainer container) { //Note: Generic register type method cannot be used here, //MainModuleFakeContext cannot have implicit conversion to IMainModuleContext container.RegisterType(typeof(IMainModuleUnitOfWork), typeof(FakeMainModuleUnitOfWork), new PerExecutionContextLifetimeManager()); }
在ConfigureRootContainer方法中,对所有环境(真实运行环境以及单体测试环境)需要用到的类型进行了注册,然后,就IMainModuleUnitOfWork而言,由于真实运行环境和单体测试环境所使用的Unit Of Work具体实现不同:真实运行环境使用的是MainModuleUnitOfWork实现,而测试环境则是使用的FakeMainModuleUnitOfWork,于是,也就在ConfigureRealContainer和ConfigureFakeContainer方法中分别作了注册。
最后,每当IContainer.Resolve方法被调用时,系统会通过读取配置文件来决定目前应该使用哪个容器来解析类型,因此,我们只需要在配置文件中正确设置容器的名称,即可在NLayerApp中使用指定的Unity IoC容器。下面这段配置信息来自于DistributedServices.Deployment项目,从中我们可以看到,NLayerApp的Distributed Services使用的是realAppContainer:
<appSettings> <!--RealAppContext - Real Container--> <!--FakeAppContext - Fake Container--> <!--<add key="defaultIoCContainer" value="FakeAppContext" />--> <add key="defaultIoCContainer" value="RealAppContext" /> </appSettings>
NLayerApp中Trace工具的实现非常简单,在Infrastructure.CrossCutting项目中定义了ITraceManager,然后在Infrastructure.CrossCutting.NetFramework项目中定义了ITraceManager的具体实现。TraceManager使用了System.Diagnostics命名空间下与Trace相关的类型实现其功能,应用程序则通过IoCFactory来获得ITraceManager的具体实现。
在上面讨论的ConfigureRootContainer方法中,NLayerApp对ITraceManager类型进行了注册:
//Register crosscuting mappings container.RegisterType<ITraceManager, TraceManager>(new TransientLifetimeManager());
因此,在整个应用程序中,就可以使用下面的方式来获取ITraceManager的具体实现,以便完成Trace功能:
ITraceManager traceManager = IoCFactory.Instance.CurrentContainer.Resolve<ITraceManager>(); traceManager.TraceError(/* error message*/);
本文对NLayerApp的基础结构层(Cross-Cutting部分)进行了研究与探讨,与这部分相关的项目有:Infrastructure.CrossCutting、Infrastructure.CrossCutting.IoC以及Infrastructure.CrossCutting.NetFramework。下一讲我们将继续研究NLayerApp的基础结构层(数据访问部分)。