新产品开发历时1年多,总算马马虎虎上线试用1个多月了,目前用户量大概300号左右,租户大概10家左右。这里提到一个“新“字,在我没来到这家公司之前其实已经有自己研发的产品(物流管理系统)在使用了,为什么还要推翻老的设计架构重新设计呢,总结主要有以下几点:
- 数据不准确(如 库存、结算数据等等,数据不准确时还找不到原因,需要手动执行sql直接修改数据库数据。)
- 维护成本太大(由于开发管理不当、需求不清晰、沟通不充分。导致项目混乱,项目表结构都很混乱,业务逻辑全是存储过程,动不动上千行代码的存储过程。)
- 速度慢(大量数据本地化处理,本地过多的查询,过多的不必要查询。)
- 用户体验差(用户不会用,功能隐蔽,反人类设计)
- 安装成本高(给用户装软件的流程是:准备软件->安装软件->调整参数->培训客户 一圈下来好多细节步骤,占据大量时间)
- 用户随便点点报错(开发没考虑好逻辑或偷懒没想好一些意外情况,如 数据为null、超出索引等等)
- 数据安全问题(如何做到保证用户数据的安全)
说实话要解决这些问题难度还是相当大的,当时在公司我能说只有我一个开发。对你没听错就我一个,据说以前的开发经理跑路了~~~哈哈哈。但是作为开发拿到这样的项目,不对,更贴切的叫法应该叫产品。应该是一个难得的机会,怎么说也得上手干一场。刚开始对公司的产品的业务非常不熟悉,许经理给我们进行了大量的业务培训。纵观系统其实很简单就是几条业务线:业务、财务、回单等等。横向技术点有:功能授权、数据授权。其实这里仅仅是表象,内部可不止这么简单。
下面我简单介绍下物流系统中的几大角色(可以简单想成在淘宝购物的物流流程):发货人、收货人、物流公司(含网点、部门、 司机、用户 )、承运公司。这里一些名词还是相对好理解一点,主要解决的就是物流公司这一块,其实物流公司更像是一个集团,网点就像子公司一样,这里的子公司之间呢又存在业务及财务上的往来,所以说里面业务、功能权限、数据权限错综复杂。要解决这些问题并非容易。
为了加快熟悉产品业务,以及理解客户的需求。一上手并没有直接就开始开发新版本,而是基于老软件进行定制开发。现在1年多过去了,我还记得定制的有几个模块还是相当复杂的,加上老软件缺乏文档,代码又不规范且没有注释,最糟糕的是全是存储过程,开发起来难度很大,总结起来有一下几点。
- 看不懂别人的代码,不明白别人的意图
- 不熟悉业务,需求理解不透彻,导致不断返工
- 项目开发中节点不明确,被客户牵着鼻子走
- 软件遗留bug多,一边开发新功能还要一边解决历史bug
- 添加新功能,新模块影响到老功能,导致数据不准确,系统报错等一系列问题
这个定制项目,断断续续持续开发了3个多月才告终,做这样的项目真的很磨人的性格。但是这个项目做下来对系统的业务更熟悉了及客户的需求更清楚了,在大哥帮组下对老系统的表结构以及系统设计思想有了进一步的认知,做项目的过程中慢慢把公司中各个同事的职责也弄清楚了。就技术层面老软件很明显的用到的技术相对简单,客户端直接数据库,几乎全部是存储过程,UI方面用的是devexpress+winform(后面简称Dev),数据层用到的微软提供的企业库EnterpriseLibrary,嗯....感觉用这个企业库有点杀猪用牛刀的意思,企业库太重了,功能非常多,但是我们只用了调用存储过程的方法。期间对企业库有过一段时间的了解,由于涵盖内容较多加上技术比较老,用的人已经不多了,所以没有刨根问底的学习这玩意,紧紧处在用的层面,有时候想想还不如一个SqlHelper呢。
经过前面的铺垫终于开始新软件的开发之路,起初2个开发,没有项目经理、美工、测试。全部就是2个开发,一个就是我。还有一个是小呆萌(我学弟,刚刚毕业没有工作经验的那种)加1个经理,经理就是我们公司的boos(后续称大哥,还是挺佩服的,集技术、运维、售前、售后、管理于一身,也是为公司操碎了心 哈哈哈)。前期大哥貌似对项目不怎么上心,虽然参与到项目的开发里面来了,还是很多地方并不是很关心,只有核心技术点上与大哥有沟通。技术上选型是这样的,还是沿用老的winform+dev这一套,但是本质有了变化新产品仅仅把winform+dev用作前端UI开发,后端服务采用的是webapi+ef,数据库采用的是sqlserver(沿用老软件的设计思想 单数据库模式即所有的用户数据用同一个数据库、同一个数据表存放)。总的技术方向有了、总的需求有了下面就需要对系统进行细化,需要搭建框架。下面就从客户端、服务器2个方向入手,细谈第一版为何这样设计以及这样设计有什么弊端,后续如何把自己埋下的炸弹给挖掉。
客户端
基本是沿用的老方法,为兼容在XP系统上运行,选用的是基于.Net 4.0开发。由于.Net的版本限制,所以很多高级特性不能使用。简单封装了一些基本使用类,如序列化类(基于json.net)、api请求类(基于HttpWebRequest类封装)、日记记录(基于log4net)、缓存帮助类(基于mencached)、根据项目需要基本类型如Datetime、Int、String等类拓展了常用方法。客户端中由于考虑到一下情况,初版我做了如下规范:
- 所有需要调用接口的地方采取配置文件的形式,当时之所以会这样设计是应为考虑到,万一服务器控制器名或方法名发生变动可以在变动代码的前提下通过修改配置文件的形式实现快开发,这个想法出发点是好的,而且非常有利于代码的维护,但是没有考虑到代码不是自己一个人写,新开发的加入无疑增加培训成本。而且我自己开发过程发现需要在代码cs文件和配置文件中2个不同的地方来回切换很不方便浪费很多时间。
挖的第一个坑【采用大量配置文件导致开发效率低、培训成本大】
服务器
这边和老软件相比有了本质的变化,老软件的设计很无脑,实实在在的CS架构,客户端数据库直连的形式再加上存储过程。这里谈到存储过程,我谈谈自己开发定制项目自己的感受,存储过程最大的优势无疑是性能强悍,为什么这么说呢。主要是存储过程直接保存在数据库里面的,仅仅需要传输参数就可以而且sql本身会对其进行查询优化,就我的开发感受而言,我还没遇到哪个技术的处理速度能够高于存储过程的性能的。但是存储过程这玩意开发体验很差,首先需要熟悉sql语法,要熟悉sql基本函数,知道游标等等。所以说这是需要一个技术相对专业的人维护才行。但是实际情况是,面对小公司想找一个技术全面的人很难得,一般的开发在处理sql层面仅仅处在select、delete、update、add、where的层面,而将更多逻辑放在业务代码中处理。而且别人写的存储过程晦涩难懂,动不动上千行的存储过程实在让人看着绝望,嗯...绝望这个词用得好~~~哈哈哈。这么分析下来还是能得出结论了,存储过程这个技术是好的,但是弊大于利,以至于本次开发新系统直接摒弃该技术。但是不用存储过程带来的问题就是性能的大幅降低。为解决以上问题,结合自己积累的技术选择了基于别人一套的快熟开发框架来进行开发。核心技术点有持久层采用的EF 数据库优先,缓存采用的是Memcache、应用服务层采用webapi。就这样服务器基本就完成了,亮点技术及坑总结如下:
- 可能自己项目经验比较少,也有可能没做过业务有这么复杂的项目,让我从开发系统无非是CRUD(增删改查)的逻辑中脱离出来。为了快速开发(原因是人手不够、自己开发封装的水平又不高),在整个应用层面我采用了大量的T4模版结合EF框架实现的超乎想象的快速开发,只要数据库设计好,接下来只需要简单的配置,整个服务代码完全ok。但是这里需要注意的是需要一些约定,如主键的后缀一般采用表名+Id的命名规范等等。这种模式仅仅适用非常简单的业务代码开发,拿这个开发产品无疑是搬石头砸自己脚,随着项目的进行,业务不断复杂,我不断调整T4模版(写T4模版比写存储过程还恶心,这辈子再也不想写了)不断的加入各种各样的参数配置,使得生成的代码更能符合业务的需要。随着业务越来越深入,后来几个版本的迭代中我不断的把此技术剔除。
挖的第二个坑【采用大量T4模版,代码写代码的模式,然而并不知道,需求无时无刻不在变化】
- 由于干掉了存储过程,这里引入了分布式缓存的概念,在老系统中是没有这项技术的。为何要引入该技术呢,前面有提到软件速度慢。在没有缓存的系统中,数据是怎么呈现到用户眼前的呢?数据是读取硬盘中的文件加载到内存中最终呈现到用户的眼前,那么引入缓存的概念呢,直接读取内存,读到数据根据缓存键直接返数据否则取硬盘数据。由于读取内存中数据的速度远大于硬盘,所以说缓存的引入无疑是极大的提升系统的性能。原理虽然是这样,如何对数据进行缓存。哪些数据需要缓存?哪些数据不需要缓存?如何管理缓存键?等等一系列问题扑面而来。在系统开发过程中需要进行缓存的总结几点:1.不经常改动的数据;2.大量使用的数据。如果在系统中发现这样的数据,那么应该将他们进行缓存处理。比如:用户信息、公司信息、菜单信息、功能信息等等都是需要反复使用而不怎么修改的数据,我们应该进行缓存处理。试想:每次请求过来要进行身份校验、权限校验、数据校验等等都查询数据库的话,数据库压力可想而知。
- 由于框架中对数据上下文(ef连接对象)在数据层进行了缓存处理,在后续的开发过程中总是会出现许多莫名奇妙的错误,比如:EF常见的报错,百度一搜一大把的那种、明明修改成功了,再次查询后却是修改之前的状态等等一系列问题。出现这样的问题,我花费了大量时间去找资料、请教前辈来解决。在搞明白基本原理的前提下,后续几个迭代开发版本中,慢慢调整框架使他更适用。
挖的第三个坑【最求高大上技术(不是特别熟悉的技术)有时候并不能有效解决问题,往往还有可能存在风险】
- 数据层核心就是EF这里我对其进行了简单的二次封装比如(增删改查),关键点就是微软推荐我们先把数据查询出来,再进行数据的更新。但是实际使用的过程中发现这样效率并不是很高,而且生成的Sql不够简洁,比如一个表多大上百个字段时,我只想修改一个字段,ef生成的sql脚本确实更新所有字段。基于以上的分析,对ef拓展了很多实用的方法,以至于到现在,这些方法都是必不可少的核心。
- 写了个辅助程序,让实体类能够自动加载数据库中的字段注释。虽然是个小东西,但是能省下非常多的时间,留下更多的时间去做更有意义的事。
数据库
数据库是重中之重,软件设计的好不好就看数据库了,有时候会多加一个冗余字段你会能够让你少写很多代码,能够大幅提升系统性能。在实际经验下发现,综合考量下来遵循范式设计的系统要比不遵循范式设计的系统就性能和用人方面而言,相差很大。其实很多时候,记住一句经典的话“把数据库设计的和excel一样!”虽然这样并不是很科学,也许这样设计在学校里或其他公司中会成为笑柄,但是实际开发过程中的经验所得,确实是如此。简单的设计会免去你很多烦恼。相比较老软件而言新架构的大变动如下:
- 数据库主键的技术选型,sql标准主键3种方案:1.guid类型(优点:全球唯一,速度快,缺点:占资源,冗长,不利于索引维护);2.自增(优点:自动编码省心,缺点:主键自增的特性 数据迁移可能会有麻烦);3.用户自定义(注意不要重复,自己定义规则)。我们选用的是用户自定义,采用的bigint也就是长整型做主键,主键统一为18位。用的是SnowFlake,我自己给他起了个别名叫“分布式id生产器”,其实这个方案是大哥极力推荐的,但是实际使用中还是面临许多问题,首先看他名字就知道不简单。既然主键在客户端生成那么他的原理还是先有客户端请求服务才能拿到一个可用的主键,而SnowFlake通过机器码和数据中心2个产生支持多个服务器部署能够满足大并发。SnowFlake原理还是根据时间和那2个产生来动态生成主键的。仔细考虑后发现,多多少少会损耗性能的,至少需要网络传输呀。还有一个弊端就是分布式id生产器宕机会直接导致系统奔溃等等一系列的问题会暴露出来。但是幸运的是目前还没有发现这样的问题。原理图参见下图

