前言:本系列文章主要讲述一个实实在在的项目开发的过程,主要包含:提出问题,解决问题,架构设计和各个逻辑层的实现以及新问题的出现和代码的重构。本系列文章以故事的形式展开,而且文章列举的很多项目的名称,大家也不用太关心,很多都是虚拟的。
新人Richard被分配到了一个企业自动化信息管理项目组--Automation Information
Management Project(后面简称AIM),当Richard进入项目组的时候,这个项目已经开始了,项目的架构也已经在两周之前构建好了--SOA架构,而且使用的主要技术也敲定了:WCF,
Linq.
注:因为项目是首次采用"新技术"(因为以前没有使用WCF,Linq,所以被称为新技术),项目就这样开始进行了。
半年之后问题就开始出现了(其实问题就一开始就出现了,只是大家还认为问题不大):因为当初在设计的时候,项目的架构是由项目组的其他两个人设计的,整个项目开发基本上就没有采用面向对象的思想来开发,而且虽然在架构设计上分了:数据层,业务层,服务层,和UI层,但是各层之前是紧紧的耦合,可以说是“牵一发而动全身”:如数据访问层稍微一改,业务层就跟着动,然后改变一层层的开始波及。
大家都开始觉得这样很累,但是项目已经做到这个阶段了,不可能重来。每次新需求一来,项目的的改动可以说是天翻地覆。而且当初设计架构的那位仁兄也就项目一开始的一个月后就走了。
下面的图就展示项目中的架构设计:
咋一看起来还是不错的,一般的架构都是这样设计的。下面就开始讲述它们之间的一些调用关系,看看有什么问题:
数据访问层:
public class EmployeeDAL { public List<Employee> GetAllEmplyees() { //... } }
|
其中Employee就是Linq生成的一个实体对象。
业务层:
代码
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->
public class EmployeeBL
{
public List GetAllEmplyees()
{
EmployeeDAL employeeDAL = new EmployeeDAL();
return employeeDAL.GetAllEmplyees();
}
}
|
服务层:
public interface IEmployeeServices
{
List GetAllEmplyees();
}
public class EmployeeServices : IEmployeeServices
{
public List GetAllEmplyees()
{
EmployeeBL employeeBL = new EmployeeBL();
return employeeBL.GetAllEmplyees();
}
}
|
然后就是在客户端生成代理,然后在UI中就调用了提供的方法。
现在一个最明显的问题就是:把数据层的数据实体Employee一层层的传递,最后到了客户端的UI代码中,而业务层中的EmployeeBL基本上没有起到什么作用,只是起到一个过渡的作用,只是在Insert
,Update,和Delete的时候,对一些字段进行了相应的Check和Validation,如Email格式是否正确等等。其他的一些流程的Check也是代码的堆积,业务类很"弱"。
总的看起来就是"牵一发而动全身"的效果。
而且在开发过程,分层的好处基本没有体现出来。
在业务类的设计的时候,所有的业务类都显得比较的"弱",之所以这么说,主要是基于这样的一个思想:
都知道,在面向“对象”设计的过程中,每个类就好比一个人,实例化一个类就好比生成了一个人,这个人可以在一出生就具备很多的能力(天生秉异),如异常处理,日志跟踪,缓存,通用的验证机制等等;也可以一出生什么都不会(或者只会做最基本的几件事情)。之前的业务类实例化之后就生成一个非常普通的人。每个类都得重写很多的基础代码,说到通用那就只是copy代码。如果想要使得新生成的类很强大,具备很多功能,在设计的时候可以让这些类继承一个功能比较强大的基类。当然继承只是实现方式的一种。
现在Richard已经被分到了另外的一个项目组(也是本系列文章要讲述的一个项目,就称为项目进度管理系统—Project
Process Management(PPM)),而且担任了架构的设计和开发(之前的架构设计Richard没有发言权)。有了前车之鉴,在新项目开发之前的几个月,Ricahrd首先就开始了通用架构的设计,目的有两个:
1.解决之前项目的问题:不灵活,不通用,每次都做重复性的事情等。
2.结合自己的考虑,开发一个Framework,使得开发更加的快速,灵活,强大。
其实在项目真正开始了之后,不可能给你几个月的时间去设计架构的。其实在AIM出现问题之后,Richard就已经在构思如果开发一个通用的Framework了(”通用”--不表示就是到处可用,因为公司的一直是开发某一领域软件的,比如现在的公司就擅长开发企业管理的一些软件,所以开发出一个基于领域模型的架构和框架还是有可能的)。Richard也想挽救AIM,由于诸多原因,想法终究只是成了想法。
在从AIM项目出来之后,Richard又开始了另外的一个项目的开发,名称我们暂时就虚拟的称为EMS(Employee
Management System),EMS项目不是很大,公司解决让Richard一个人开发这个项目。这个项目给了Richard很多的时间来考虑架构设计和Framework设计的时间,因为EMS项目不是很复杂,而且技术和进度都在掌控之中,在正常上班时间就可以到时候定期交付。所以每天下班之后,Richard开始加班去构思Framework的设计,开发的时间越长,技术就应该沉淀的越多,如以通用类库,组件的方式或者解决问题方案的文档等出现。只有这样,下次的开发才更加的快速。
3个月下来,EMS项目完成了。而且Richard设计的Framework也有了雏形。准确的说,还只能称为
基础架构基本完成。EMS没有采用这个Framework来开发,因为Framework的设计和实现于EMS是同步进行的。
Richard心里是这样认为的:设计通用的架构,然后在项目中不断的锤炼,更新,产生出通用的代码,然后演化为Framework。只有设计出了自己的Framework,以后的开发才有可能进入"光速开发"。
在这个项目开始之初,Richard就和其他几个组员讨论了如何实现,同时也推出了自己开发的成果。商量之后,决定采用Richard的设计。
Richard在设计架构的时候,也参考了现在流行的一个Framework,如Spring.NET ,CSLA.NET,
Nhibernate,主要吸收它们的一些思想,同时也分析了这些Framework对自己项目的利弊。而且认为:没有绝对万能的技术,一个架构的实现需要在很多的因素之间权衡,技术不是用来show的,而是用来解决问题,这就是技术的价值。
1.数据层草图的提出
Richard开始着手设计,一开始他没有就立刻在自己的计算机开始敲代码。而且采用笔+纸开始构思。
因为他认为:写程序不是什么时候都得上机,脑子里面想什么的才是最重要的,往往很多时候,在设计程序时,首先在头脑中就已经把整个功能已经实现了,甚至代码的详细编写都已经在头脑中走了一遍,并且在头脑中”运行,调试”了。
开始设计了,因为这次Richard想要提出一个比较好的架构,一个比较强大的企业级的架构,所以参看成功的一些案例是很有必要,Richard也就想到了微软best
practice的那些推荐的架构组织方式和建议(大家对best practice不熟悉也要紧,不会影响阅读)。
之后,Richard的第一个草图就出来了:
一个架构组织方式的提出,不是随随便便就提出的,新的架构的设计和提出首先必须要明白你要解决哪些问题,而且也不要”过度设计”。(这个过程很难,很多时候需要权衡,所以作为架构的设计者,权衡的思想很重要:在时间,资源,资金等都要考虑)。可能在起初会参看一些别的设计架构,甚至是模仿它们,但是随着思考的深入,那些表象的东西就会逐渐的被抛除。
同时也开始设计的时候,没有说一定要立刻就要设计出一个很强大的东西出来,对架构设计的能力也是在慢慢的演化和思考过程中提升的。
2. 对数据访问层的思考
在解释为什么架构要像上面那副图进行设计之前,我们首先来讨论一些之前项目问题:
对于数据访问层(DAL)的问题:
1. DAL很依赖Linq生成的实体。可以说在之前的项目中,在数据访问层能够使用的技术就已经”钉死”在了Linq上。这里不是说Linq不好,而且强调在DAL的访问技术的选择的余地已经没有了,不灵活。
a) 在架构的设计过程中,就需要考虑到以后技术的转变和更换,可能在项目A中采用Linq to sql,但是在项目B中就采用Entity
Framework。因为我们的目的就是要开发一个比较灵活的通用架构,能够支持不同就数据访问技术。可能以后的项目都只是用一种访问技术,但是最为架构的设计者,特别是希望从架构最后能够演化到Framework,
那就要为更换技术预留接口。
2. 在DAL中没有很多的异常处理等底层机制。
a) 在项目设计的过程中,有些底层的机制是几乎每一个逻辑都要用到的:异常处理,日志跟踪,缓存机制,事务机制,安全验证机制。当时在之前的DAL中是没有的。可能现在你认为有些机制不是需要的,或者不明白为什么需要。
因为一个强大的软件,不能随随便便就因为某些异常或者错误就崩溃了,也不可能就是一大堆代码的堆砌。上面所提到的有些机制:如异常,日志,它们的价值很多时候在软件维护的时候体验出来。根据日志记录,可以查处软件哪里出了问题,如是数据库断了,还是哪个操作流程导致了问题。
而有些机制是在运行时体现价值,如缓存,验证,事务。
但是在使用这些底层机制的时候也要权衡,综合的考虑,如缓存机制,就得明白那些数据要缓存,缓存在哪里,缓存数据时候要加密,缓存多长时间,如何刷新过期了的数据。等等,很多东西要考虑。
3. 数据来源仅仅只是考虑了数据库。其实这个问题不是之前的项目的一个问题,但是这里有必要提出。
a) 一般在我们开发项目的时候,数据的来源很多时候都是数据库,我们直接操作数据库就行了,但是还得考虑一个问题:如果我们的项目没有自己的数据库,我们的数据来源是来自其他的公司或者服务接口,怎么办?作为架构的设计者,是需要考虑这些的。
可能在项目敲定那天就已经清楚是自己设计数据库还是从其他地方获取数据。但是一个通用的一个架构的就要能够为其他的数据源预留接口。
这里,可能就有了一点”过度设计”的味道了,起初在项目A中所使用的架构没有考虑其他数据源的问题,但是如果在项目B中出现了,怎么办?那么架构就要演化,改进来适应这种情况。
之前也提过,没有必要一上来就设计强大的就架构,而是在项目中改进,演化。开始时候没有考虑到,以后慢慢的加嘛。(比较的纠结)。
上面只是紧紧的从数据层DAL的角度进行了思考,DAL最终还是为业务层BLL提供数据的,所以就考虑DAL以何种方式来被BLL调用,鉴于之前的一些考虑,可以得出一点:不让BLL直到DAL的实现细节,所以接口似乎是个不错的解决办法,Provider的模式也似乎可以排上用场。
于是,Richard就决定在DAL和BLL之前加上接口层来解耦。
3. 第二个数据层草图的提出
这个图和之前的第一个图基本上是一样的,只是做了一修改,而且加上了之前谈论中涉及的一些问题。
因为随着思考的深入,逐渐的发觉:数据源的来源可以很多—数据库,普通文本,XML等等。不同的数据源提供不同的Provider,其实从其他的服务接口获取数据也是一种来源,上面的图之所以单独的把Service
Agent分出,主要是因为比较特殊。
而且图中的那些基本功能:Log, Exception等,是到处都用到的。
此时Richard也在头脑中闪现了一些要处理的,可以出现的异常:
1. Data Source is not exits:数据源不存在,因为这个问题很常见,比如在项目运行过程中,数据库断了,或者远程的服务无法访问,等等基本都属于这个问题的。所以这个异常肯定是要处理的。
2. Time out:超时。这个异常很常见,获取的数据过大,或者远程数据源连接超时,等,都可以引发一些问题。
3. 如果数据源是其他服务接口提供数据,那么在数据获取时,可能要转换数据格式,如果格式出错,。或者发送的数据不符合一些规则,这个异常一定要处理,因为这些数据可能涉及到安全的问题了:是数据真的不正确,还是被篡改了。
程序就必须对这些异常进行处理:是把原生的异常抛出,然后有业务层决定如果处理;还是决定把异常包装称为另外的一个异常,再抛出;还是最后干脆再DAL就执行自己处理,然后给业务层一个友好的提示,等等。如果数据源是远程的服务,那么如果服务断了,在异常过程中,采用什么样的策略:简单的处理,如抛出异常,记录日志,还是做一些恢复性的操作,如尝试重新连接,等。
之前第一张图中,在DAL上定义一个接口,用来接耦,但是在第二张图有做了一些修改--把DAL做为服务提供出去。之所以做了这个修改,因为在开始思考的时候,只是认为底层设计的DAL只是给BLL使用。可以发觉到一点:在DAL中,数据可以从远程的服务中获取,那么可能我们这里的DAL也可以作为服务给其他的人设计的DAL使用,也就是说,我们的这里的DAL成了远程服务的数据源。所以做了上面的修改。但是这个思考到还会改进,因为这里面问题(读者朋友可以先自己思考是什么问题)。
1. 草图的一些问题在哪里
当Richard把草图画出来了之后,想到了另外的一个问题:在DAL数据层之间提供的那个接口层到底应不应该是Services
Interface。其实这个接口层是普通的Interface层还是Services Interface,Richard也拿不定主意的,因为当初之所以要把这个接口层改为Services
Interface,是因为在数据源提供者(Service Agent)那块给了他“灵感”——数据源可以使用远程的Services。基于这个思想,所以Richard也考虑到了:也许,现在设计的这个DAL,哪一天会作为服务给其他的程序提供数据也不说定。
虽然,这个问题对现在来说不是那么的重要,但是在Richard的心里,无法说服自己到底使用哪一种接口层(也许是Richard这个人的性格有关吧,一定要给个理由说服自己,但是这个理由又不能随随便便的糊弄自己)。
Richard想到了之前在开发项目的时候,也确实曾经把其他公司提供的服务作为数据源的情况。当时的调用虽然只是进行查询,增加,删除,修改的简单操作,但是很多的流程已经在服务提供者那边定义好了,例如在发送一批货物的时候,Richard只是调用了服务接口的一个CreateProduct(Product
product)方法,但是在服务器那端却做了很多的事情:计算库存,生成订单,选择货物供应商等等。这样说来,如果现在Richard把DAL上面加上一个Services
Interface层,那么DAL或者其他的层就必须提供很多的逻辑操作,或者不一定是逻辑操作,还可以是数据格式验证、身份验证。
如果真的这样设计,那么数据层的做的事情就很多了,要很多的逻辑。而这些逻辑在BLL中进行才是比较好的选择,想到这里,Richard似乎开始明白:把Services
Interface层放在BLL层之上。这样就可以充分的利用BLL的逻辑验证功能。所以DAL之上的接口层,还是决定采用普通的接口。
2. 重审之前项目中数据层的问题
Richard在数据层DAL这块花了大量时间来思考,其实是有原因的。在之前的项目中,数据层的设计显得很臃肿,而且在Richard看来,这些代码已经可以用一种比较通用的方式来写,没有必要写那么复杂的代码。
例如,在EmployeeDAL中有以下的方法:
public Employee GetEmployeeById(string employeeId); public Emplpyee GetEmployeeByName(string employeName); public string GetEmployeePositionById(string employeeId); public int GetEmployeeCount();
|
这样写,没有错。但是有一点引起了Richard的思考:DAL类中,很多的方法基本上都是查询的方法,而Add,
Update, Delete在很多的DAL类中形式都比较的统一,特别是在使用了Linq, Entity
Framework之后,所有的数据实体类Add,Update, Delete方法几乎是一样的,如在Linq中可以使用DataContext.GetTable<T>().InsertOnSubmit(entity)方法来插入任何的数据实体类。
但是就只有这些不同条件的查询方法很难统一,几乎是每个不同的DAL都要去实现自己的一些特有的查询方法。但是Richard认为把这些方法统一是有可能的,甚至是达到那种“以不变应万变的”效果才好,带着这个想法Richard开始进行很多的尝试。
3. 思维的一点突破
Richard极力的在自己的脑海搜寻解决的方案,也翻阅自己之前下载和买过的书籍,看看那些是否与查询数据有关,只要看到有“查询”两个字的章节,Richard就不会放过。也参看了.NET的一个知名开源Framework
的设计思想。
给了他提示的就是Fowler<<企业应用架构模式>>一书,书中提到的查询对象(Query
Object), Richard参看了之后第一反应就是:确实不错,但是太抽象了,没有给出一些示例。
其实之前Richard在看这本书的时候,认为书的高度太高了,Richard几次攻读,都是深受打击。所以,结果是Richard很敬畏这本书,但是还是想有朝一日能够拿下它。现在书中就有了查询对象的一些解决方案和实现,Richard硬着上了。
Richard认为:很多的事情不是等你的所有条件都具备才去做的,事情往往是在你还没有准备好的情况下就已经发生了,凡事都有一个开头,做架构也是这样的,开始设计一个架构的时候,并且不是说明你已经完全的把所有技术都掌握了,完美了,肯定是有一个开始尝试的过程。可能在开始设计的时候,漏洞百出,但是思维的周密性也是这样慢慢的锻炼出来的。
平时在思考的时候,自己站在一个高一点的角度看问题(现在是开发人员,但是开发人员在设计和开发的时候可以这样想:如果我是架构的设计者,我会怎么做?),思维的高度上去了,机会到来的时候也就从容了。
Richard开始重新理解查询对象。其实查询对象就是在一定程度上隐藏了SQL语句,查询对象是解释器模式的一种应用,实际上查询对象最后还是要解释为SQL语句到数据库中去执行的(不管在中间过程是如何一步步的操作这个查询对象,因为数据库现在只是认识SQL,所以查询对象最终还是要生成SQL语句)。
查询对象主要用途就是使得客户程序可以构造各种的查询,而且查询中使用的都是与业务类有关的属性,这就意味着,客户程序不用了解数据库的表名和列名。这种做法最直接的好处就是业务开发的人员不用知道数据库的构造。
而且如果查询对象的设计是以接口的形式出现,那么灵活性就更加的大。例如,设计一个IQuery的查询对象接口,然后,在架构中有两个实现了IQuery接口的查询对象,如LinqQuery,
EFQuery,这两个具体的查询对象最终会被分别解释为Linq和Entity Framework可以识别的语句,再通过Linq和Entity
Framework去执行数据操作。在程序中就可以通过配置来决定使用哪种查询对象和使用哪种数据访问技术。
越来越多的想法在Richard的脑海中出现,而随之带来的问题也开始堆积。基本的思想是有了,但是实现起来那就得是另外的一回事了。怎么把这些思绪理清,怎么把这些理清的思绪变为代码的实现,这个成为了摆在Richard面前最现实的难题。
但是不管怎样,已经有了一点点的进展,记得当初考虑的时候还是头脑一片空白,现在的想法一个接着一个,在思维上进步了。Richard觉得有点欣慰。
4. 回首再看数据访问层
有了上面的思考,Richard再次审视了DAL,开始发觉:DAL其实就只是一个存取数据和操作数据的地方,这就是DAL的主要功能。现在数据层的设计基本上已经可以实现了,而且可以做到“以不变应万变”,不管BLL中对数据进行什么样的操作,在DAL层都可以用那个四种增,删,查,改的方法搞定。
1. 设计DAL的基本操作
Richard认为:在设计一个架构或者Framework的时候,有几点很重要:
a. 总体把握的能力。
b. 抽象的能力。
c. 分析的能力
首先,从总体上来看,Richard认为DAL中最基本,而且最容易想到的方法就是CRUD(Create,
Read, Update, Delete)四个操作。
于是Richard在草纸写出了基本操作的名称:
AddSingleDataEntity;
AddDataEntityList;
UpdateSingleDataEntity;
UpdateDataEntityList;
DeleteSingleDataEntity;
DeleteDataEntityList;
GetSingleDataEntiry;
GetDataEntityList; |
上面列出的方法名字很长,其实Richard在思考这些方法的名称的时候也参考了.NET设计规范中的一些建议:方法名称要具有“自解释性”,因为架构的设计最后还是给开发人员用的,所以方法的定义要一眼就看出它是干什么的,而且规范的命名也可以大大的减少维护的成本。(可能这些名字的命名有点对规范的“生搬硬套”,但是之后会慢慢的重构的)
从总体出发,已经定义出了基本的操作,那么现在就开始一步步的分析,如何实现这些方法。
Richard开始思考第一个方法的实现,其实Richard心里也清楚:其实到底哪个方法作为第一个来考虑也许很重要,但是在一切都不清楚之前起码要拿一个来入手,而且随着思考的深入,很多的问题都会慢慢的浮现,到时候一切就会明晰起来。
对于AddSingleDataEntity这个方法,首先就要考虑这个方法到底要把什么添加到数据库中,也就是说要考虑这个方法的参数,而且这个参数要足够的“兼容”,因为之前Richard就是想设计出一个“以不变应万变”的DAL。在考虑这个参数问题之前,首先Richard很清楚:在.NET数据访问技术中,Linq,Entity
Framework等ORM技术已经广泛的应用,所以在设计DAL的时候要充分的考虑到现有的一些技术(尽量避免重新造轮子)。
而且在数据是如何被存入到数据库中的以及数据是如何从数据库中取出的,这个工作是完全可以交给这些ORM的,最后的结果就是:在DAL中只是操作这些ORM的那些映射实体。基于这个想法,
Richard就确定了:AddSingleDataEntity参数是一个数据实体。(本系列文章约定:数据实体,即DataEntity,就是ORM对一个数据库表进行映射后产生的实体和数据库中的表一一对应,如在数据库中有一张Employee表,Linq
to Sql将会把它映射成为Employee的一个类,这个类就称为数据实体)。因为这些方法最终是操作数据实体的,所以包含这些方法的接口名字就定义为IDataContext。
因为不同的表产生不同的数据实体,但是Richard还想使得AddSingleDataEntity这个方法可以接受任何的数据实体,所以此时很有必要对数据实体进行抽象。所以Richard想到了定义一个接口:IDataEntity,打算让所有通过ORM生成的数据实体都继承这个接口。而且Richard还想到:
1. 如果BLL直接引用DAL使用的,那么IDataEntity可能会在BLL中出现的。
2. 如果BLL通过repository去DAL中获取数据,那么到时候BLL可能都不会直接引用DAL,但是BLL最终还是得使用数据做事情,所以IDataEntity还是会在BLL中出现,所以,IDataEntity接口最好定义在一个公共的地方。
Richard决定新建一个Common的类库,加入IDataEntity接口的定义,现在这个接口里面什么都没有,只是一个标记而已,表明继承这个接口的类就是数据实体类。
AddSingleDataEntity(IDataEntity dataEntity); |
还有一点就是尽量的使用类型安全的方法,于是Richard把方法改成了范型方法:
AddSingleDataEntity<T>(T dataEntity) where T:IDataEntity,class,new(); |
于T 的那些约束:T:IDataEntity,class,new(),是考虑到了Linq和EF中对数据实体的一些要求。
一般的Add方法都是返回添加是否成功,true或者false,方法再次改造:
bool AddSingleDataEntity<T>(T dataEntity) where T:IDataEntity,class,new(); |
然后Richard就写出了上面列出的一些方法的定义:
bool AddSingleDataEntity(T dataEntity) where T : class,IDataEntity, new();
bool AddDataEntityList(List dataEntityList) where T : class,IDataEntity, new();
bool DeleteDataEntityList(List dataEntityList) where T : class,IDataEntity, new();
bool DeleteSingleDataEntity(T dataEntity) where T : class,IDataEntity, new();
bool UpdateSingleDataEntity(T dataEntity) where T : class,IDataEntity, new();
bool UpdateDataEntityList(List dataEntityList) where T : class,IDataEntity, new();
|
至于GetDataEntityList,按照之前的查询对象的想法,传入一个IQuery的接口:
List<T> GetDataEntityList<T>(IQuery query)where T : class,IDataEntity, new(); |
2. 对基本的操作的进一步的思考
确实,上面那些基本操作是没有什么问题的,现在Richard又考虑到了另外的一些问题,还是以AddSingleDataEntity方法为例:
a. 有些时候,不仅仅要知道插入数据是否成功,而且还想返回新加入数据在数据库中的主键信息来做其他的用途。怎么办?再来查询一次?
b. 如果插入失败了,仅仅只是返回一个false吗?可能其他的调用模块想知道到底是发生了什么异常而导致的插入失败,而且其他的模块对于发生的异常有自己的处理方法,所以AddSingleDataEntity要提供足够的信息。
基于上面的思考,所以这个基本的操作方法不能只是简单的返回一些简单的值就完了。也就是说,这些方法要返回一个数据包:里面包含很多信息,以便其他的调用模块来使用这些信息,感觉有点像是C#事件中的eventArgs.
所以Richard在Common的那个类库中加入一个对象,定义如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/-->public class DataResult where T : IDataEntity
{
public List EntityList { get; set; }
public bool IsSuccess { get; set; }
public Exception Exception { get; set; }
public object CustomData { get; set; }
}
|
这个类最后将会作为方法的返回结果。
Richard发觉,上面的方法确实是很有”自解释性”,但是很多的开发人员对这些CRUD操作的名字都很熟悉了,而且最后开发的出来的架构的使用者还是开发人员,应该符合他们的习惯,所以Richard改变了方法的名字,改变后的版本如下:
代码Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/--> public interface IDataContext
{
IList Query(IQuery queryCondition) where T : class,IDataEntity, new();
IList Query(IQuery queryCondition, int pageIndex, int pageCount) where T : class,IDataEntity, new();
// CRUD services (well, mostly CUD)
T Add(T item) where T : class,IDataEntity, new();
IList Add(List itemList) where T : class,IDataEntity, new();
bool Delete(List itemList) where T : class,IDataEntity, new();
bool Delete(T item) where T : class,IDataEntity, new();
T Update(T item) where T : class,IDataEntity, new();
List Update(List itemList) where T : class,IDataEntity, new();
}
|
3. 查询对象的一些思考
在上面的基本的操作中,在Query方法中传入的是查询对象,因为查询对象是基于解释器的,可以在解释完查询对象之后就缓存起来。以后再次查询的时候,如果两个查询的对象一样(例如在构造查询对象时候,可以为其定义一个唯一的标示),那么就不用再去对查询对象进行解释。如果根据这个查询对象的查出的数据都是只读的,那就更好了,就可以把查询对象和对应的结果一起缓存起来。
本篇文章涉及技术不多,主要是些想法(代码部分正在实现)。
最主要的改进主要在于业务层开发的简化。
大家都知道系统的核心就是业务逻辑层了,业务逻辑的代码往往得我们一行行的敲代码,而且不同的系统,每次都得一行一行的重头来写,这样的开发的效率可想而知了。基于这个原因,所以很有必要用一些工具和方法来加速开发。
经过这两天的思考,个人认为最大的改进就是:业务逻辑类的图形化配置。我先给出图的示例,然后再详细的说明这个思想。
大家知道,在一个BLL类中包含了大量的业务规则和验证规则,而且这些规则往往需要手工来写,特别是一个业务类的一些属性,要对他们的数据类型,长度,格式,状态更改跟踪,是否可以读写等进行验证,虽然说现在已经有了一些类库可以使用,如Enterprise
Library中的Validation组件,但是我们还是要写代码,如果把这些规则通过图形化的拖拽的方式,或者图形的配置方式来生成C#代码,然后需要定制的部分再手动的修改,这样开发就简化多了(甚至可以把自动生成的代码和需要手写的代码分别放在partial类中,大家可以想想VS中开发Window程序时,VS就把那些自动生成的代码放到了另外的一个partial类中)。用过Enterprsie
Library的朋友已经很清楚那个图形化配置工具的威力。
首先,来看第一个图(设计的草图)。
当我们新添加了一个业务类的以后,假设这个业务类的为 Product,在”解决方案”管理器中点击右键,选择”配置业务类”,就会弹出图形化的配置窗体,从草图中可以看出:
1. Properties,为业务类添加的属性。
2. DataProvider,这个配置表明了业务类使用哪个数据提供者:AdoDotNetProvider,LinqProvider,EntityFrameworkProvider.(这些Provider就是之前DAL系列文章最终会完成的那些Provider)
3. MappingType这个业务类和那个数据来源体映射,这里有两个选择:DataTable,DataEntity(Linq实体或者EF实体,DataTable
的改进是来源于金色海洋和virus,thanks)
4. MappingTypeName:这个配置项就表明这个业务类会和哪个数据来源体的属性进行映射,如,在上面选择了MappingType为DataEntity,而且选择DataEntity的名字MS_Product(通过Linq生成的实体,对应数据库中的MS_Product表)。当然,一个业务类的属性字段可能来自多个DataEntity的组合,所以,这里的MappingTypeName是多个。
下面来看第二个草图。
这个图的出现时当点击了第一个图中的Properties出现的,这个界面就是专门来配置业务类的具体的属性的。
Name:就是属性的名字。
MappingTo:就是指出要和哪个数据实体的哪个属性映射。(之前第一个界面已经制定的数据实体)。
ValidationRules:用来指定为这个属性使用哪些规则。
下面就来看看第三个草图。
这个草图和之前的第二个草图类似,只补过这个界面是专门用来给特定的属性配置验证规则的。
就介绍到这里了,希望大家以后多多提出意见,便于进一步的改进,最后开发的Framework会以源代码的形式给出的。
|