wawaCRM项目业务层框架设计草稿
好多业务层框架都使用的哑对象做业务实体,比如PetShop,我想我们的架构别用这个了,我们应该让业务实体可以执行一些方法,而这些方法实际的执行交给其它的类的代码来具体执行,就是以(面向服务)SOA的方式去执行,为了获得高可伸缩性,我们的服务类可以直接连接并使用,也可以使用Remoting远程连接使用,这样就可以让业务服务层专门的运行在一台应用服务器上,而且也为实现负载平衡提供了可能,而我们的表示层呢,只需要引用实体层,然后调用实体层上的方法,就会自动执行另一台机器上或者远程的业务服务层的代码。业务服务层的代码还可以使用一些.NET计数器类,来查看客户端的连接数,某个服务方法单位时间内调用的次数,同一时间内有多少个并发调用,某个业务服务类在内存中有多少个实例等等这些都可以进行监控和管理(因为对AOP不太熟悉,所以这些地方我想咱们就先直接硬编码到业务服务类里面)。
我们先来看直接连接的时候的情况,我们示例一个查看图书作者的报酬的小程序。首先在客户端程序代码里引用EnterpriseObjects和System.Data两个命名空间。然后在客户端程序启动的时候设置数据库连接字符串,如果是桌面程序就在第一个启动窗体的构造函数里设置,如果是ASP.NET程序就在Application_Start方法里设置。
EnterpriseApplication.Application.ConnectionString = "integrated security=sspi;initial catalog=bookmanager;data source=onlytiancai";
然后在Load方法里填充一个作者下拉列表,如下。
// 获取所有的图书作者列表,这里使用的是强类型的DataSet,
//而不是普通DataSet
AuthorSet authors = Author.GetAll();
// 遍历每一个作者
foreach(Author author in authors)
{
// 把它填充到一个List控件里让它显示
listAuthors.Items.Add(author);
}
我们可以看到,这里直接把Author类Add到List控件里了,默认的是使它的ToString()方法,所以我们在Author类里要重写这个方法,这样才能让下拉列表里显示有意义的字符,否则只显示的Author的类型名,代码如下。
// Name property
public string Name
{
get
{
return FirstName + " " + LastName;
}
}
// ToString
public override string ToString()
{
return Name;
}
下面我们来分析一下原理哦。Author.GetAll方法实际上继承自AuthorBase类的GetAll方法,而AuthorBase类呢,是代码生成器自动生成的,咱们的这个代码生成器可以直接生成GetAll(),GetById(),Insert(),SaveChange(),GetRelatedXXX()之类的方法,因为像这些方法大多数实体都需要的,获取所有记录,根据ID获取一条记录,插入记录,修改数据,根据外键获取某个记录的相关子数据等等,这些重复性的没有技术含量的动作应该由工具生成,而且每次数据库的结构变化了,比如说添加了个字段,或者字段的顺序变了,类型变了,需要重新用代码生成器生成一下代码,然后项目重新加载一下生成的.cs文件,同步一下就自动更新了业务层代码了。但是你自己添加的一些方法和属性都写在实体类里,比如Author类,而不是AuthorBase类里,因为你写在AuthorBase类里,同步的时候就给你覆盖了,写在Author类就没事,而Author继承自AuthorBase类,其它实体也一样,Customer继承自CustomerBase,CustomerBase自动生成。
我们来看一下AuthorBase的GetAll()方法。这里呢,把这个方法又重定向到了service.GetAll()方法上,而这个service来自AuthorBase类的一个静态属性SericeObject里,而这个静态属性是调用的一个GetServiceObject方法来返回业务服务类的。
public static AuthorSet GetAll()
{
AuthorService service;
service = ServiceObject;
return service.GetAll();
}
public static AuthorService ServiceObject
{
get
{
return ((AuthorService)(GetServiceObject(typeof(AuthorService))));
}
}
其实呢,AuthorSet类呢,继承自EnterpriseObjects.Entity类,EnterpriseObjects.Entity类是架构里的类,定义了所有的业务实体的一些方法和属性,所以呢,业务层的项目需要先引用EnterpriseObjects类库。上面的GetServiceObject方法就是EnterpriseObjects.Entity类的一个静态方法,我们来看看它是如何执行的。
// ServiceObject - return our service object...
protected static Service GetServiceObject(Type serviceObjectType)
{
// do we have the cache?
if(_serviceObjects == null)
_serviceObjects = new Hashtable();
// get it out of the cache...
Service serviceObject = (Service)_serviceObjects[serviceObjectType];
if(serviceObject == null)
{
ServiceObjectFactory factory = EnterpriseApplication.Application.ServiceObjectFactory;
serviceObject = factory.Create(serviceObjectType);
// add it...
_serviceObjects.Add(serviceObjectType, serviceObject);
}
return serviceObject;
}
可以看到,所有的业务服务类被高速缓存在一个HashTable里,客户端要获取服务类的话,先在_serviceObjects的HashTable里查找有没有被缓存,如果没有的话,就实例一个EnterpriseApplication.Application.ServiceObjectFactory类,然后调用它的Create方法,这个方法返回一个EnterpriseObjects.Service类,这里使用的是工厂模式(工厂模式的原理就不再说了,因为偶不懂,呵呵)。创建了Service类后还把它添加到HashTable缓存里,以供下次使用。ServiceObjectFactory类是个抽象类,不能直接实例化,而EnterpriseApplication.Application里使用了一个单件模式,如下
private static EnterpriseApplication _application = new EnterpriseApplication();
public static EnterpriseApplication Application
{
get
{
return _application;
}
}
然后它的ServiceObjectFactory属性实现起来比较复杂,但是非常关键,我们来看。
// 创建一个服务对象工厂
public ServiceObjectFactory ServiceObjectFactory
{
get
{
//是否已经有了一个服务对象?
if(_serviceObjectFactory == null)
{
// 检测连接字符串的各个部分,查看是直接连接还是使用Remoting
if(ConnectionStringParts.Contains("Enterprise Connection Type") == true)
{
string connectionType = ConnectionStringParts["Enterprise Connection Type"].ToString().ToLower();
switch(connectionType)
{
// 直接类库连接…
case "direct":
_serviceObjectFactory = new DirectServiceObjectFactory();
break;
//远程访问….
case "remoting":
_serviceObjectFactory = new RemotingServiceObjectFactory();
break;
// 否则...
default:
throw new NotSupportedException("Connection type '" + connectionType + "' not supported.");
}
}
//是否获取一个服务对象? 如果没有就用直接连接创建一个...
if(_serviceObjectFactory == null)
_serviceObjectFactory = new DirectServiceObjectFactory();
}
// return it...
return _serviceObjectFactory;
}
}
可以看到,上面这个属性是根据EnterpriseApplication.Application.ConnectionString这个字符串来判断如何实例化业务服务类的,本例就是直接连接的,上面的这个属性里调用了其它的三个属性和方法,我们分析一下ConnectionStringParts返回一个哈希表,它拆分连接字符串,并把每个部分以键值对保存起来,以便能直接访问连接字符串的各个部分。这里的remoting连接方式是自创的哦。详细方法很简单,我贴一下代码
// ConnectionStringParts property - split up the connection string...
public Hashtable ConnectionStringParts
{
get
{
// do we have it?
if(_connectionStringParts == null)
{
// create it...
_connectionStringParts = new Hashtable();
// split it...
string[] parts = ConnectionString.Split(';');
foreach(string part in parts)
{
// split into value...
string[] valueParts = part.Split('=');
_connectionStringParts.Add(valueParts[0], valueParts[1]);
}
}
// return...
return _connectionStringParts;
}
}
我们在本篇文章开头看过一个数据库直接连的字符串格式,我们再来看一个remoting远程连接的格式。
Enterprise Connection Type=remoting;appname=BookManager;port=8080;protolcol=tcp;servername=ONLYTIANCAI
可以看到这里提供了Remoting连接所需要的remote名,端口,协议,主机名等必要的信息,限于篇幅,这里不介绍更多的原理了,再介绍利用web服务实现复杂平衡的时候再详细介绍客户端如何获取remoting连接字符串。
而里面的DirectServiceObjectFactory类和RemotingServiceObjectFactory都继承自ServiceObjectFactory抽象类,前者非常简单,Create方法用System.Activator.CreateInstance直接实例了一个服务类,如下。
public override Service Create(Type type)
{
return (Service)System.Activator.CreateInstance(type);
}
而RemotingServiceObjectFactory就比较复杂了,需要解析remoting连接字符串,提取服务器名,端口,协议这些,然后注册信道,最后远程实例化一个服务类,这里面还要涉及一些异常捕获和处理的问题,由于时间问题,我不贴了先,下次单独介绍这块儿。
回到开头哦,我们这个例子是根据作者查看作者的报酬,到这儿,作者已经填充到一个下拉列表里了,我们要选择列表里的一个作者,然后把作者的详细报酬列表绑定到一个DataGrid里。
gridPayments.DataSource = null;
if(listAuthors.SelectedIndex != -1)
{
// 从列表控件里获取当前选定的作者,因为绑定的时候
//我们是直接绑定的Author对象,所以这里获取对象很简单
//进行一下强制类型转换就行了,这就是类型化数据集的好处
Author author = (Author)listAuthors.Items[listAuthors.SelectedIndex];
// 获取作者的报酬
DataSet payments = author.GetRelatedPaymentDetails();
// 在网格里显示报酬细目
gridPayments.DataSource = payments;
}
注意,这里的author.GetRelatedPaymentDetails方法也是间接调用的AuthorService类的一个方法,而这个方法是代码生成器生成的,你在数据库里给Authors表里与Payments表建立一个外键约束,代码生成器就会自动生成这些,如果再复杂的多表连接,代码生成器就不能生成了,那样的话,自己写存储过程,然后用CMP来管理。
好了,先介绍这些,这个架构有些老,当时泛型和AOP技术都不是很流行呢,而且我对这两种技术不是很了解,以后了解了再改进这个架构,其实这两种技术用好了,写业务逻辑或者应用框架的时候是很舒服的。我演示的这个例子基本上涵盖了wawaCRM项目业务层的基本行为,大家给看看这个架构在使用上会有哪些问题,给我反馈一下,我好改进,我的电脑昨天晚上已经装好VWD2005和windows2003了,今天晚上再装个vc#2005和金山词霸就基本就绪了,这几天我会好好的整合架构,现在持久层框架(CMP),业务层框架(本文),表示层框架(MastPage+wind的门户框架)都有个雏形了,争取十一月份我们正式开始做需求,编码。