针对业务场景的解决方案
针对业务场景的解决方案
这是一个系列专题,是根据我所经历的具体的业务场景所用到的解决方案和技术架构设计的一个汇总。
一方面是对一直以来的经历做一个总结,在以后做项目的时候有一个基础的想法。
另一方面是秉承这开源的精神,希望大家在接到甲方爸爸的需求时候,起码一个基本的解决方案的思路方向,不至于心里没底。
如果针对某一场景,在以后的工作中有更好的解决方案,我也会更新进来并说明原因及优劣。
基础篇
基础篇包括表设计原则,API设计原则,工程结构设计原则,事务设计原则
表设计原则
锁的设计
乐观锁和悲观锁都是经常使用的锁,现在大多使用的都是乐观锁,悲观锁由于其特性(一人编辑,其他人无法做任何操作,包括查看)很少被使用,这里不作赘述,现在主要是对于锁存在的情况下,异步更新数据所引发的问题。
场景:
在一个订单系统中,用到的数据库是TiDB,采用乐观锁。订单在提交后,需要将数据导出到外部系统,并且在外部系统生成外部系统订单号,由于外部系统的响应时间不确定,因此需要做成异步。
问题:
由于TiDB的默认事务级别是
快照隔离,并不是他文档上描述的可重复读。因此在主线程事务没有提交时,异步线程开启,此时主线程的事务还未提交,这时候订单的版本号并不是最新的,如果异步事务根据这个版本号更新,则会发生无法更新的异常,异步事务直接结束。解决:
订单增加回传状态flag相关字段,并将异步回传的事务提取出来,与订单能正常提交的事务拆开,两个事务放在controller层。在确定订单提交事务的提交后,再进行异步事务。是否成功更新回传flag。
预防:
两手准备:
- 应把异步字段单独提取出来形成一张表,表中不应含有锁字段
版本号。这样可以解决锁的问题- 在调用异步方法时,应直接传入最新的对象,不应传入对象的主键ID在再异步方法中查出数据。这样可以解决数据库隔离级别的问题。
字段的设计
对于业务单据来说,字段主要分为两大类:状态字段、值字段、管控维度字段。状态字段包括新建状态,提交状态等;值字段包括金额,数量等;管控维度字段包括公司,业务实体,采购组织等。
状态字段:
状态字段需要记录三点:状态FLAG,状态操作人,状态操作时间
如果头状态需要根据行状态算出来,则要根据具体业务判断头状态是设置FLAG还是CODE
状态需要分为状态CODE和状态FLAG,某种状态可以结合当前状态及标志位算出来。头状态的数量一般为``行状态数量阶乘-1(n!-1)`注意不要落下。
状态操作人如果有多个,建议做成行转列,之后进行扩展也比较容易。
值字段:
值字段包括金额,数量等,这些在数据库中要注意小数点位数,java中注意要中BigDeciaml类型,防止精度丢失。
管控维度字段:
管控维度字段比较有争议的地方就是应该放在头上还是行上。这里需要看业务的具体要求和单据类型。以采购订单为例,供应商自然是要放在头上的,而物料肯定是要放在行上的。
然后是头行上都有管控字段。这种情况一定要和产品商量好,如果行上字段的数据不一致,头应该怎么处理。举个订单上税率的例子,行上有税率(行税率),头上有税率(头税率),当行税率不一致,头税率怎么取(整张订单该以何种税率结算?)
还有一点就是冗余设计。这种字段一般都配有code和name,是否需要在当前业务表上冗余这些字段,则要看单据是否要存储当前时间点信息,则需要冗余;如果要和管控字段的实时信息一致,则不要冗余。
API设计原则
API需要符合restful设计原则,这里对restful不作解释,只对细节作一些说明。
API连接符
eg. PUT /v1/{orderId}/batch-enable
这是一个简单的restful风格的API,其中有几点需要作下说明
- 连字符需要用中划线
-,不要使用batchCreate因为使用驼峰某些古老的浏览器不支持。下划线也不要使用,URL中的hostname不允许使用下划线,统一风格使用-;而且用户体验不好(只按-键和shift+-键哪个更简单一点?)。- 最好是加上版本号控制
/v1,以后如果升级大版本可以直接v2,对外兼容性好,不需要再改动原本url- 启用禁用功能不要让外部传递flag,而应该是系统暴露启用禁用端口。
工程结构设计原则
使用DDD工程结构,可查看DDD项目结构(领域驱动设计)。方法封装粒度按照infra->domain->app的顺序从低到高。
事务设计原则
主要是两点,默认事务与独立事务,独立事务需要谨慎使用。