NBearV3 Step by Step教程——IoC进阶


简介


   本教程在《NBearV3 Step by Step教程——IoC篇》的基础上,演示如何基于NBearV3的IoC模块开发一个分布式Web应用程序的过程。您将看到,基于NBear的IoC组件,开发分布式系统就和开发单服务器系统一样容易。本教程同时将引导您注意分布式开发和非分布式开发,在实体定义中的注意事项。

    注1:NBearV3提供的分布式支持,从用户视角来说,只要按照《NBearV3 Step by Step教程——IoC篇》的方式,以定义本地服务接口和实现相同的方法定义和实现服务接口,再进行一定的配置和部署,就能在不修改代码,甚至不需重新编译的情况下,使应用程序轻松具有分布式能力,并可以以Service为单位进行多服务器分布部署,且能够由ServiceMQ Server控制,自动实现负载均衡。在NBear封装的逻辑内部,是以ServiceMQ Server为消息中心,基于.Net Remoting进行消息传递,并使用Castle作为IoC容器实现的。

    注2:在阅读本文之前,建议读者先阅读《NBearV3 Step by Step教程——IoC篇》以掌握NBearV3中有关ORM和IoC的基本知识。

    目标

   通过本教程,读者应能够全面掌握使用NBearV3的IoC模块开发单服务器/分布式应用程序的全过程。

    代码

    本教程演示创建的所有工程和代码,包含于可以从sf.net下载的NBearV3最新源码zip包中的tutorials\IoC_Adv_Tutorial目录中。因此,在使用本教程的过程中如有任何疑问,可以直接参考这些代码。

    Step 1 下载NBearV3最新版本及准备

   1.1访问http://sf.net/projects/nbear,下载NBearV3的最新版本到本地目录。

    1.2 将下载的zip文件解压至C:\,您将看到,加压后的NBearV3目录中包括:dist、doc、cases、src、tutorials等目录。其中,在本教程中将会使用的是dist目录中的所有release编译版本的dll和exe和tutorials目录中之前的IoC基础教程。

    1.3 将tutorials目录中的整个IoC_Tutorial目录复制到任意其它位置,并命名为IoC_Adv_Tutorial,我们将以IoC_Tutorial为基础,演示NBearV3中基于IoC的分布式开发的知识。

    Step 2 扩展设计实体及元数据

   2.1 将IoC_Adv_Tutorial中的IoC_Tutorial.sln重命名为IoC_Adv_Tutorial.sln,并在VS2005开发环境中打开。

    2.2在本教程中,对于从IoC_Tutorial继承过来的这些工程,我们会做很小的一些修改,您将注意到,我们做这些修改的原因,并不意味着,一个非分布式系统必须做经过修改才能以分布方式部署。而是,我们将引导您注意,在基于NBear的分布式系统中,实体定义和Service接口设计的重要注意事项。

    2.3 首先,需要注意一个在分布系统中的实体设计规范:两个实体或者多个实体间,要避免双向/循环可读写、可序列化的引用。

    具体举例来说,如果您打开EntityDesigns中的EntityDesigns.cs文件,您将注意到,Category和Product,互相包含了可读写的引用。这会有什么问题呢?在非分布式系统中,只要两个引用不同时是LazyLoad=false,这就完全没问题,您在IoC Tutorial中已经看到了,程序运行得很正常。但是,在分布式情况下,因为,Service的中的方法的参数和返回值,会被序列化后,以消息的形式进行传递。所以,以这里的Category和Product为例,假如我有一个Product的实例,现在我把它序列化,此时会发生什么呢?他的属性Category也会被序列化,序列化这个Category属性时,又会发生什么呢?他的Products属性也要被序列化!!问题来了,我们最初的Product实例,肯定也包含在他的Category属性的Products中,所有又会被序列化。。。这样就死循环了。

    怎么办呢?办法很简单,至少将一个引用设为只读(设为只有get没有set)或不可序列化(为属性标注SerializationIgnoreAttribute)。在这个Category和Product的关系中,比较合理的是将Category.Products属性设为只读,代码如下:

[MappingName("Categories")]
public interface Category : Entity
{
[PrimaryKey]
int CategoryID { get; }
[SqlType("nvarchar(15)")]
string CategoryName { get; set; }
[SqlType("ntext")]
string Description { get; set; }
byte[] Picture { get; set; }
[FkQuery("Category", OrderBy = "{ProductName}", Contained = true, LazyLoad = true)]
[SerializationIgnore]
Product[] Products
{
get;
set;
}
}

    此时,序列化Category时,就不会序列化他的Products,从而就能避免序列化的死循环。也就能正常用于分布式系统了。

 Step 3 从实体设计代码生成实体代码、实体配置文件

   3.1 至此,所有的实体的设计就修改就完毕了。编译EntityDesigns工程。

    3.2 运行dist目录中的NBear.Tools.EntityDesignToEntity.exe工具,载入EntityDesigns工程编译生成的EntityDesigns.dll。

    3.3 点击Generate Entities按钮,将生成的代码保存到Entities工程中的一个名叫Entities.cs的代码文件。

    3.4 点击Generate Configuration按钮,将生成的代码保存到website工程下的名为EntityConfig.xml的文件中。

    Step 4 使用ServiceMQServer.exe和ServiceHost.exe,部署程序为分布式系统

   4.1 在将测试程序部署为分布式系统之前,我们先验证一下程序运行正常。将website设为启动工程,并设置Default.aspx为启动页。运行website,看看,Default.aspx是否正常显示了和IoC_Tutorial中完全相同的运行结果。

    4.2 为了更方便测试,我们在IoC_Adv_Tutorial目录中建一个Bin目录,Bin目录中建立ServiceMQServer目录和ServiceHost目录。新建如下的脚本文UpdateAssemblies.bat,用来更新所有需要的dll和exe到两个目录下:

@echo off
copy ..\website\EntityConfig.xml .\ServiceHost\ /Y
copy ..\Entities\bin\Debug\*.* .\ServiceHost\ /Y
copy ..\ServiceImpls\bin\Debug\*.* .\ServiceHost\ /Y
copy ..\ServiceInterfaces\bin\Debug\*.* .\ServiceHost\ /Y
copy ..\..\..\dist\NBear.IoC.Servers.ServiceMQServer.exe .\ServiceMQServer\ /Y
copy ..\..\..\dist\NBear.Common.dll .\ServiceMQServer\ /Y
copy ..\..\..\dist\NBear.IoC.dll .\ServiceMQServer\ /Y
copy ..\..\..\dist\NBear.Net.dll .\ServiceMQServer\ /Y
copy ..\..\..\dist\NBear.IoC.Hosts.ServiceHost.exe .\ServiceHost\ /Y

    4.3 执行4.2所见的脚本,复制相关程序集到这两个目录。

    4.4 在ServiceMQServer目录中,我们看到,除了NBear.*.dll之外,只有一个文件NBear.IoC.Servers.ServiceMQServer.exe。这个文件是NBear提供的,从dist目录复制过来的。我们需要为它创建如下的NBear.IoC.Servers.ServiceMQServer.exe.config文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="serviceFactory" type="NBear.IoC.Service.Configuration.ServiceFactoryConfigurationSection, NBear.IoC" />
</configSections>
<serviceFactory type="Remoting" name="testServiceFactory" protocol="HTTP" server="127.0.0.1" port="8888" debug="true" maxTry="30" />
</configuration>

    以上的配置,指定了允许连接到该Server的ServiceFactory的配置信息。其中参数含义分别为:

    · type - ServiceFactory的类型是Remoting,默认情况下,ServiceFactory的类型总是Local的,所以不能连接远程ServiceMQServer。
    · name – 用于连接ServiceMQServer的唯一名称,该名称不能包含空格。
    · protocol - ServiceFactory连接ServiceMQServer的协议,可选值为HTTP或TCP。
    · server和port – ServiceMQServer监听的服务器地址和端口。
    · debug - 是否在ServiceMQServer中显示调试日置信息。
    · maxTry - 对于同一个消息的等待读取的最大次数。

    4.5 我们再切换到ServiceHost目录。该目录下包含了用于部署Service的程序集。我们可以看到,有ServiceInterfaces.dll,ServiceImpls.dll,Entities.dll,相关的NBear和Castke程序集,和NBear.IoC.Hosts.ServiceHost.exe。最后这个程序也是有NBear提供,从dist复制过来的。我们需要为它创建如下的NBear.IoC.Hosts.ServiceHost.exe.config文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
<section name="serviceFactory" type="NBear.IoC.Service.Configuration.ServiceFactoryConfigurationSection, NBear.IoC" />
<section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common"/>
</configSections>
<entityConfig>
<includes>
<add key="Sample Entity Config" value="~/EntityConfig.xml"/>
</includes>
</entityConfig>
<castle>
<components>
<!--You can use standard castle component decleration schema to define service interface impls here-->
<component id="category service" service="ServiceInterfaces.ICategoryService, ServiceInterfaces" type="ServiceImpls.CategoryService, ServiceImpls"/>
<component id="product service" service="ServiceInterfaces.IProductService, ServiceInterfaces" type="ServiceImpls.ProductService, ServiceImpls"/>
</components>
</castle>
<serviceFactory type="Remoting" name="testServiceFactory" protocol="HTTP" server="127.0.0.1" port="8888" debug="true" maxTry="30" />
<connectionStrings>
<add name="Northwind" connectionString="Server=(local);Database=Northwind;Uid=sa;Pwd=sa" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
</connectionStrings>
</configuration>

    我们可以注意到,配置文件中除了包含对ServiceFactory的配置,参数的含义和4.4中的config含义完全一样。另外,这里也包含了我们从IoC_Tutorial中复制过来的website中的Web.config中类似的entityConfig、ConnectionString和castke配置节。之所以要配置这些信息,是因为,我们的ServiceHost将作为Service的宿主,接受对他支持的service的访问请求,要读取实体信息,也需要访问数据库。    

 4.6 接着,为了让website能够访问远程Service,我们需要为website的Web.config添加serviceFactory配置节,同时为,为了演示同时存在本地Service和远程Service的情形,我们保留castle配置节中的category service。修改完的Web.config内容如下: <?xml version="1.0"?>
<configuration>
<configSections>
<section name="serviceFactory" type="NBear.IoC.Service.Configuration.ServiceFactoryConfigurationSection, NBear.IoC" />
<section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common"/>
<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
</configSections>
<entityConfig>
<includes>
<add key="Sample Entity Config" value="~/EntityConfig.xml"/>
</includes>
</entityConfig>
<castle>
<components>
<!--You can use standard castle component decleration schema to define service interface impls here-->
<component id="category service" service="ServiceInterfaces.ICategoryService, ServiceInterfaces" type="ServiceImpls.CategoryService, ServiceImpls"/>
</components>
</castle>
<serviceFactory type="Remoting" name="testServiceFactory" protocol="HTTP" server="127.0.0.1" port="8888" debug="true" maxTry="30" />
<appSettings/>
<connectionStrings>
<add name="Northwind" connectionString="Server=(local);Database=Northwind;Uid=sa;Pwd=sa" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
</connectionStrings>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies></compilation>
<authentication mode="Windows"/>
</system.web>
</configuration> 
     4.6 如果您想在多个服务器上测试本程序,您可以分别将ServiceMQServer和ServiceHost目录中的内容复制到不同的服务器。但是,需要注意修改所有的config中的server地址修改为ServiceMQServer所在的服务器地址。

    当然,如果只是想先看看运行效果,你也可以直接在本机运行。

    Step 5 运行分布式程序

   5.1 现在我们就可以运行整个程序了。我们首先必须先运行ServiceMQServer.exe。

    5.2 接着,我们运行两个ServiceHost.exe实例(如果您愿意,也可以运行更多)。您将能看到,在ServiceMQServer.exe的窗口中,会显示,分别由两个ICategoryService和IProductService的订阅者。他们自然是我们的ServiceHost向ServiceMQServer订阅的。

    5.3 运行website,并访问Default.aspx,你将能看到website的运行结果应该和没有部署为分布式程序之前的结果实完全一样的。您可以刷新几次页面,并注意ServiceMQServer和ServiceHost的窗口。

    您将能看到,对IProductService的请求,会被自动发送给两个ServiceHost中的一个来处理并返回,但是,你看不到ICategoryService被处理的日志。为什么呢?因为,我们在website的Web.config中的castle块中保留了本地的category service组件定义。在Default.aspx请求某个Service时,如果,ServiceFactory发现有本地实现,则会直接返回本地Service实现的实例,如果找不到本地实现,则会向ServiceMQServer发送Service调用请求,ServiceMQServer,则将把对Service的调用请求负载均衡地,转发给注册到它的ServiceHost。所以,在多刷新几次页面的时候,您将注意到,有时,请求是被一个ServiceHost处理的,有时,请求被另一个处理。

    如果你将Web.config中的category service那个component注释掉,再次刷新Default.aspx页面,则您将能看到,对category service调用,也会被发送给ServiceHost处理。

    但是,注意,此时,Category.Products总是返回null。为什么呢?因为,我们在2.3中将Category.Products属性设为只读了。只读属性是不会被序列化的,所以Products不会被传递到远程。

 

本文作者:
« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3