重新整理了一下过去开发的框架,在准备开发新项目时候,重新整理了一下思路,感觉数据访问层还是很鸡肋。过去几年中,急于完成项目开发和交付,框架都是迭代过来的,虽然满足了开发需求,但是,从安全性、复杂度等方面看,还是有不足的。
从安全性角度看,查询参数和提交数据的安全校验必须增加。虽然已经使用了对象化参数传递查询参数,防止了SQL注入的情况,但是,数据合法性校验依然没有深度去实现。从过去的实践中发现,前端输入数据的随意性,例如应该是整数,用户输入了字母,但是前端没有做严格的数据限制,或者,前端就是模板生成的代码,校验不严谨,导致系统保存数据的时候,会直接报错。虽然底层对这些报错的情况做了拦截和记录日志,但是严重影响了系统性能。
复杂度主要是指框架迭代过程中的一些冗余代码。数据访问层的设计,是基于通用性和框架性两个大方面来考虑的。通用性就是不限定映射实体的具体类型和格式,相当于兼容常规的系统开发。但是,从框架角度,特别是业务层面,这些通用的映射实体是很难满足系统框架需求的。因此在这个基础上,增加基于框架设计要求的实体基类,例如包含:
string recordId 数据行的唯一记录,通过uuid(guid)生成字符串
DateTime createDate 记录的创建时间,不允许修改
DateTime updateDate 记录最后修改时间
string createBy 记录创建人ID,这里是员工的编号,不允许修改
string updateBy 记录最后修改人的ID
bool deleted 记录软删除标记,true表示记录被删除
还有其他一些字段的定义,就不一一列举。
系统开发离不开与数据库的交互,框架设计初衷就是最大化简化数据交互的方法,降低开发的随意性和复杂性。
一般来说,系统开发尽量不要直接使用数据库脚本进行数据交互。dotnet中,有丰富的orm可以选择使用,例如ef,dapper,sqlsugar,freesql等等,但是,学习这些组件,也需要花费不少时间,这是其一。其二就是程序员可能直接操作组件,进行任意性的数据操作。从数据库优化角度看,尽量单表查询,减少join表查询,然后程序员有时候为了方便,直接就来join,甚至是好几个表join,也没有注意锁的应用,在数据增加到一定程序,就出现死锁情况。
orm提供了丰富的lambda方法进行查询,也避免了写错字段的情况。框架把insert\update\delete\get\getlist\getpagelist等方法进行了封装,可以减少95%以上的操作orm的机会,通过封装一层,屏蔽掉了orm的使用,也降低了程序员的学习难度。
这次重构数据访问层,主要从以下几点考虑:
一、兼容读写分离。这里两个概念,一个是,有些业务需求,数据是只能读的,例如第三方同步过来的数据。框架增加了Read/Write两层数据访问层,出现只能读的数据,则继承只读的基类即可;二个是主从数据库的读写分离,业务流程的功能代码,使用写的数据库,分析用的数据,使用读的数据库。前段时间自己开发了一个同步软件,把sqlserver数据同步到了clickhouse,读写分离不限制数据库的类型,有利于按需求选择数据库和优化系统开发。
二、提升分布式事务的安全性。防止某个微服务宕机导致请求等待的情况,例如某个微服务已经接受了分布式事务,但是,突然就宕机了,无法接收提交事务的请求或者回滚事务的请求,导致数据的一致性被破坏。
三、业务表单支持多个从表的通用访问。在面向对象中,不确定的数据访问层,无法赋予对应的泛型来确定对象,如果使用没有泛型的数据访问对象,根本无法使用lambda进行查询,因此,把数据访问层的接口,拆分成三组,一组是没有泛型的,一组是带实体泛型的,一组是使用逆变泛型的。这样,无论在什么情况下,都可以使用一个统配实体接口,来描述数据访问对象。
四、更换目前的orm,过去使用ef、chloe,但是使用过程中,存在各种问题,比较了几个orm,选择freesql,比常规的orm支持更多的数据库类型,特别是clickhouse.但是替换难度有点大,尽量轻量化,为不同的数据库要建立不同的接口或者组件,按需动态加载,这里和注入有点不一样,只是用于解析不同数据或者交互的,从开发层面上看,这一层应该是透明的。
五、强化正则表达式的提交前的数据校验。除了可以在实体属性上定义规则外,还允许在数据访问层添加或者制定验证规则。例如字段的长度、合法内容等,在不同的业务中,可能相同的字段会存在不同的校验规则。
以下是重新设计后的数据访问层结构图。框架设计把数据归为3类
1、基础数据,例如员工信息、部门信息这类的
2、配置数据,流入多对多的关联表
3、表单数据,例如采购、销售等业务单据