关于常量
在代码中出现数字或者具体的字符串,绝对是一件不好的事情。但是如果业务复杂,常量很多,那么怎么组织常量又是一个问题。
其中有两个问题要考虑,第一个是命名。如果模块很多,那么为了区分模块中某个业务下的某个常量,常量名称会边的巨长,显得很臃肿。第二个是常量的聚合。如果常量很多,可能会有很多常量是按需才写的,导致表达同一意义的常量分布在很多地方,如果之后阅读代码,就完全搞不清楚某处的数据(比如状态)到底有多少种。
对于这个问题,解决的办法是,把每个意义的常量,写在一个单独的类中。可以根据模块,业务,状态等维护,使用静态类嵌套的方式,组织常量。或者直接分多个类也行。
包括错误码也可以这么处理。
关于接口参数
Controller向页面传递参数,service层向controller层传递参数,或者rpc调用,以及微服务的http接口调用等,不管什么场景下的接口,使用map传递参数都是一个绝对的错误。每次查看接口能提供的参数,就要阅读一遍程序找到map中有多少数据,是一件多么痛苦的事。
同时一般接口都会返回给的信息,比如接口调用结果,失败原因,错误码等信息,如果使用map很容易就会遗漏某个信息。
解决这个问题,可以创建一个公共的出参类,把一些公共的参数定义好,之后所有的接口的出参,都继承这个类,就可以解决问题。
这种办法虽然会定义很多类,但是对于代码规范来说,是很有用的。
关于数据推送
项目中有一个场景,需要将我们的数据推送至内网里面别的系统中,所以涉及到很多数据推送的问题。
开源的消息组件有很多,activeMq,rabbitMq,kafka等,但是使用开源软件,很多东西就不可控了,比如业务校验失败,数据处理失败等,所以我们采用程序加数据库的方式来实现消息的推送。
但是数据推送还是可以参考消息队列的设计思想。主要的注意事项有业务解耦,异步发送,失败的补偿机制。
同时由于有些业务对推送要时序要求,比如分中心没有推送成功,则禁止推送合同,所以我们在推送模块中加入了数据校验的功能,同时增加了推送成功或者失败之后的回调,通知业务模块调用结果。
设计思想:
1. 异步
这个主要是因为数据推送一般是推送到其他的项目中,所以可能会出现延时或者网络中断以及对方不在线等各种问题。所以如果是同步发送,那么接口的响应时间会很长。
2. 补偿机制
可以分为自动补偿和手动补偿。比如定时扫描的方式,或者人工界面查询失败记录,手动点击发送。
3. 业务解耦
数据推送应该和业务没有关系。不应该和业务代码紧紧的耦合。做为一个独立的接口或者模块存在。
数据结构
1. 接口表,目标(其他系统的标识),操作(新增,修改),地址(url),其他信息(秘钥等)
2. 推送记录表,任务标识(哪一种推送),推送数据,目标,操作,推送结果,重试次数,创建时间,上次推送时间,推送成功时间,接口返回数据, 回调类名称,校验参数,处理参数。
具体的处理逻辑
1. 业务代码将数据放入表中,包括推送的目标等数据
2. 推送模块反射出回调对象,调用其中的校验数据的方法,并传入校验参数,如果校验通过,则进行下一步。
3. 定时任务扫描推送记录表,推送数据,并调用回调类的处理方法,传入处理参数
4. 如果执行失败,则稍后重试,如果手动删除,则按成功处理,并回调处理方法。
关于数据库设计与表关联问题
项目中我们尽量将模块之间解耦,所以模块之间不允许表的关联查询。但是这样很多关联查询要依靠程序完成,效率很低。所以总结下出现的问题:
1. 模块内部关联,模块之间数据库隔离
2. 除了业务字段,最好再加上记录创建/更新时间,创建人,等可以追溯的参数。即使业务不要求,也可以在出现问题时找到操作人。
3. 字段适度冗余。这个适用于两个场景:
(1) 模块关联过长。比如分中心下关联合同,合同下关联款项,如果要查某个分中心下的款项,则需要三级查询才能查到款项。然而实际业务里,这三个的关联关系一旦入库就不会再变动,分中心编码,合同编号也不允许修改,所以如果在款项中记录分中心编号和合同编号,查询起来会容易的多。
(2) 模块内业务行为多。比如订单模块,我们分为审核,发货,改单,退单等行为,每个行为都会有一个历史表,比如审核历史表,发货状态表等,但是实际上经常在查看订单时,需要查看其最新的一个状态,如果可以在订单表中加入这些字段,查询起来会方便的多。
4. 提供细化查询。一般大的查询耗时很长,但是如果是提供给其他模块,可能只是用到其中的几个字段,没有必要把整个模块的信息都查出来给调用者。
5. 善用缓存。由于不使用表关联,所以可能出现遍历调接口的场景,这个时候,如果能够使用缓存,速度也可以很快。
6. 合并查询。对于负责的查询业务,将多个查询整合在一起有助于加快速度。
关于业务逻辑分层
对于某些特别复杂的模块,可以适度的将业务层再继续分层。比如将业务校验放在一起,将数据处理放在一起。或者将独立的业务逻辑单独分开,便于代码复用等。
我们项目中,遇到两个问题,最后都只能拆分代码处理。
1. 第一个是有些模块做了很多业务限制,比如新增报单,停用启用等。这些功能是给分中心用的,产品也没设计关于角色和权限的考虑。然而项目完成了,新需求来了,要求客服有最大权限,可以操作任何功能。最后只能把这个模块的代码根据业务校验和数据处理分开。
总结下就是,业务层再分为两层,底层是纯数据层。操作这个功能所有的数据处理,不考虑业务的限制。然后在上层做限制,再根据业务需要,开发出多个业务接口,提供给不同的角色使用。
2. 第二个是单个处理与批量处理的问题。比如删除,最早做的是单个删除,如果删除过程中出现错误,就直接抛异常。但是后面要加一个批量删除,结果在批量接口里面,如果捕获了删除接口的异常,则整个接口还是会回滚,因为事务已经检测到了异常。
所以只好把删除的逻辑往下再写一层,不在事务的范围内。这样单个删除和批量删除都调用底层的删除接口。
关于异常处理
1. 异常处理尽可能的统一,当需要改动时比较方便,而且可以方便的将异常信息发送给开发,便于查找问题。
2. 自定义异常中传入的参数,可以和接口的参数保持一致,便于调用段统一处理。接口的出参可以新建一个公共类,放一些公共的数据。
关于特殊数据处理
项目中有很多特殊数据,需要特殊对待。现在项目的场景就有很多个了,比如:
1. 下订单时,某个特殊分中心可以选择一个特殊的课程。
2. 某些消息需要发给客服这个角色。
3. 某些账号是超级管理员权限的。
4. 某个业务线需要特殊处理
项目中不能直接在程序中写死这些数据,但是如果在其对应表中加字段,又会造成字段极大的浪费,而且随着业务的开展,会不时的出现类似的需求,加字段无法彻底的解决问题。
解决方案是,可以提供界面或者其他方式,给这些特殊数据加上标签,然后再根据标签去判断。
标签表字段:id 主键,data_id 某条数据的id,如分中心id,param_name 标签名称,param_data 标签值 可省略,comment 备注说明
这样做的好处是:不用改变原有的数据结构,也不会有字段浪费,但是这样做的后果是,可能因为要特殊处理某一个数据,导致要把所有的数据都遍历一遍,所以这个必须得有缓存,否则处理的慢,数据库也受不了。
关于配置以及元数据
项目中有很多数据的配置以及初始化的数据。主要可以这么分类
1. 各种框架的配置文件
2. 各种规则的配置
3. 项目的基础数据,比如各种状态的数据。
4. 与其他系统的对接数据,如别的系统,短信配置,邮箱配置
个人认为,能写在数据的配置,最好都写在数据库中,毕竟可以不停机修改,如果写在配置文件中,每次修改都要重启项目。
而且,最好能将配置统一安置,千万不要到处放的都是。我做的一个项目,由于管理不严,所以每个人都有自己放配置的地方,结果就是数据库有将近十张表都是配置数据,配置文件有好多个。每次需要新增配置,就怎么简单怎么来。结果就是,如果要迁移项目,或者创建新的环境,就需要在百张表中找出那十个配置表,然后再给运维写个文档,告诉配置文件该修改哪些地方,异常痛苦。
关于项目管理
每个规范的制定,都是为了执行效率或者代码规范,以及可以更容易的阅读代码。但是有些方式会增加工作量,这是不可避免的,可以利用其他手段规避其缺点。
但是开发良莠不齐,一切只为开发简单的人多的是,项目管理,代码审核很重要。如果任其自由开发,那最后项目只会越来越乱。毕竟自己的代码都有看不懂的一天,何况别人的代码。
做的一个项目,一个女生在写审核模块的时候,审核状态全部用的数字,结果后来我去看她的代码,有8种审核状态,具体是什么意思,看不懂。为什么我能看出是8种呢,因为我看到了8的存在。而且将近十层的if判断,代码还没有自动对齐。业务逻辑分布在代码的各个层里面,完全没有分层的概念。虽然我是单身狗,但是对这种女开发也是完全不动心的,这辈子离她越远越好。