组件可以理解成一个系统中的插件,基类可以理解成系统的地基,一个好的地基,能够承载更多更复杂的业务实现。
很多程序员在理解开发框架上,都是认为是技术的堆积,例如微服务的服务发现、单点登录、权限验证等,我认为,开发框架不需要高大尚的技术,而是需要能满足开发需求和支撑业务需求的就足够了。
记得最早写系统的时候,还是用foxpro之类的,甚至更简单的开发工具,那个时候哪里有什么技术支撑,都是靠10个手指狂敲代码。后来用了powerbuilder,visual basic、visual c++或者c++builder、asp、delphi等开发工具,真正把以前的c++面向对象初步应用起来。到最后彻底转向.net,从1.0到现在的dotnet core。整个过程不知道写了多少系统,又有多少系统在我面前倒下,说起来都是泪。
20年的代码生涯,积累了不少业务经验和编码经验,敲坏了很多很多个CV键,成也CV败也CV,导致很多时候这些V过来的代码没改彻底,出现了很多问题,还不容易发现bug。
2008年开始,写了平生以来的第一个orm,用了不到2年以失败告终。但是这没有关系,这个时候搭建起来了开发框架的雏形。
一开始还是基于winform开发的,在UI、BLL、DAL上做了很详细的封装,首先是解决简单查询通用性的问题。常常看程序员写代码,查询就花费非常多的时间,例如:
if(!string.IsNullOrEmpty(queryParm.id))
{
query = query.Where(t=>t.ID == queryParm.id);
}
if(!string.IsNullOrEmpty(queryParm.name))
{
query = query.Where(t=>t.Name.Contains(queryParm.name));
}
var list = dbContext.Query(query);
只要客户提出需求增加一个查询条件,则需要程序员修改这里的代码,非常麻烦。 其实可以把查询条件封装成对象,每个条件都是对象,例如:
//构造查询条件的集合
var condition = new Dictionary<string,IclsBase>();
var idCondition = Util.CreateString("[TableName]","=",new List<string>{"123",456});
var nameCondition = Util.CreateString("[TableName]","like",new List<string>{"张","陈"});
condition["ID"] = idCondition;
condition["Name"] = nameCondition;
var pagelist = dal.GetList(condition,1,10,"CreateDate desc");
前端请求查询,也是执行这样的标准,那么,只要前端增加一个查询控件,按查询结构向后端请求查询,就能很好的完成查询请求。
解决这个问题后,就可以把查询的方法封装到基类了,我们把常用的方法都放到基类中,例如:
public interface IDALBase<T> where T:class,new()
{
RValue<T> insert(T item);
RList<T> insert(List<T> items);
RValue<T> update(T item);
RList<T> update(List<T> items);
RValue<int> updateRange(Expression<Func<T,bool>> where,Expression<Func<T,T>> updateFields);
RValue<T> delete(T item);
RList<T> delete(List<T> items);
RValue<int> deleteRange(Expression<Func<T,bool>> where,bool realDelete);
RValue<T> getEntity(Expression<Func<T,bool>> where);
RList<T> getList(Expression<Func<T,bool>> where,string orderBy);
RList<T> getList(Expression<Func<T,bool>> where,int page,int size,string orderBy);
RValue<T> getEntity(Dictionary<string,iclsBase> where);
RList<T> getList(Dictionary<string,iclsBase> where,string orderBy);
RList<T> getList(Dictionary<string,iclsBase> where,int page,int size,string orderBy);
}
在业务层(BLL)和数据访问层(DAL)都封装常用的方法,这些方法其实大家都会写。再在WebApi的基类中也封装对应的接口,那么,当创建一个新的项目后,对应的增删改查完全不需要另外写代码了,都是默认带的接口。
在ERP之类的系统中,最常见的是基础数据和表单数据,我们在上述封装的基础上,针对这样数据结构的业务进行封装,例如产生编号的方法、产生单号和明细序号的方法,这些都是一样的,只是规则不一样,但是有一点,都会使用到流水号,那么就封装一个流水号产生方法,这个方法可以是全系统唯一,也可以是当前微服务唯一,具体就看实际需要了。
public class BaseBillBLL<THdr,TDtl>:BaseBLL
{
protected virtual void createFormno(THdr hdr){}
protected virtual void createSn(TDtl dtl){}
public virtual RValue<THdr> saveBill(THdr hdr,List<TDtl> dtls){}
//其他一些查询、编辑、删除表单的方法,以及封装工作流审核的接口。
public virtual RValue<THdr> GetHdr(string formno){}
public virtual RList<TDtl> GetDtls(string formno){}
....这里不一一列举,但是这里必须封装常用的方法,例如获取列表等
}
public class BaseInfoBLL<TInfo>:BaseBLL
{
protected virtual void createId(TInfo item){}
//这里同样要封装各种查询和保存的方法,这些方法主要是为WebApi调用时候准备的,在开发中一般使用数据层直接操作数据库
}
其实业务层和数据层也会有一些共性的属性或者方法,那么再建立一个基类
public class LibBase
{
protected CYBSession Session; //关于token的一个kv容器
public CYBContext Context; //上下文对象,包括员工编号、角色编号、用户编号等
//在开发中,两层用到的其他公用的方法、属性等,都可以放到这一层,也为扩展提供了便利
}
public class BaseBLL:LibBase
{
}
public class BaseDAL:LibBase
{
}
再配合各种工具类,封装一些固定的规则,例如微服务之间的通讯、分布式事务、Redis缓存操作、文件操作等等,具体的,看设计思路不同做集成。在业务表单上,再做一个工作流的插件集成,这样只要提供一套工作流的操作方法,程序员就能快速开发工作流应用。
这样的封装满足了基本的业务开发,程序员在开发的时候,根本就不需要接触到什么ORM等各种各样的插件,也不需要掌握什么技术,框架提供的各种方法,已经足够使用了,如果需要另外的一些操作,可以向更高一级的程序员申请开发新的框架接口或者获取更底层的操作权限。
基类封装其实是为单体程序做基础的,里边可能会集成一些微服务的功能,其实没有冲突,最好让程序员也感觉不出微服务的开发逻辑,只需要提供一些标准方法就可以了。不管是微服务还是日后扩展的继承性平台或者更复杂的系统,都需要从最简单的结构开始堆砌功能。只有底层稳定才能更好地放手去做其他开发和扩展。
当然,这样的底层基类封装,需要设计和开发的程序员或者架构师更多参与到日常开发中,积累各种各样的需求和经验,能够自主掌握一些技术和技巧,需要开发者有较多的积累。
文章没有提及Controller的基类封装,主要也就是一些标准接口,还封装了一些路由规则和插件启动的程序,和一般的dotnet core中的startup差不多,只是一般应用只需要建立一个空的startup类,继承基类startupBase就可以了,这里没有什么技巧。