针对业务系统的开发,如何做需求分析和设计?
需求分析
- 积分获取
- 积分消费
- 积分查看
- 积分过期
用户通过某些渠道(消费、签到)等获取积分,然后会有一个积分查看页面,可以查看当前可用积分以及积分过期时间;之后用户可以使用积分(兑换物品、现金);对于积分来说,不同的渠道获取的积分可能有不同的有效期。
- 对于一个功能或者系统,可以先去借鉴和了解一下其他人的产品是什么样的。大概了解其功能。
- 大概了解后就能有一个笼统的功能设计,但是对于细节还是可能会有遗漏;
- 可以借助“线框图、用户用例(user story)”来细化业务流程。
- 用户故事侧重情景化,模拟用户如何使用我们的产品,描述用户在一个特定场景的使用流程。
- 例如关于积分有效期,可以进行如下设计:
- 用户在获取积分的时候被告知有效期;
- 用户在使用节分的时候,会优先使用快过期的积分;
- 用户在查询积分明细的时候,会显示积分的有效期和状态(是否过期);
- 用户在查询总可用积分的时候,会排除掉过期积分。
系统设计
主要聚焦在架构层面设计(主要是“类”)。
1.合理的将功能划分到不同的模块
对于前面罗列的所有功能点,我们有下面三种模块划分方法。
第一种划分方式是:积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护(增删改查),不划分到积分系统中,而是放到更上层的营销系统中。这样积分系统就会变得非常简单,只需要负责增加积分、减少积分、查询积分、查询积分明细等这几个工作。我举个例子解释一下。比如,用户通过下订单赚取积分。订单系统通过异步发送消息或者同步调用接口的方式,告知营销系统订单交易成功。营销系统根据拿到的订单信息,查询订单对应的积分兑换规则(兑换比例、有效期等),计算得到订单可兑换的积分数量,然后调用积分系统的接口给用户增加积分。
第二种划分方式是:积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护,分散在各个相关业务系统中,比如订单系统、评论系统、签到系统、换购商城、优惠券系统等。还是刚刚那个下订单赚取积分的例子,在这种情况下,用户下订单成功之后,订单系统根据商品对应的积分兑换比例,计算所能兑换的积分数量,然后直接调用积分系统给用户增加积分。
第三种划分方式是:所有的功能都划分到积分系统中,包括积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护。还是同样的例子,用户下订单成功之后,订单系统直接告知积分系统订单交易成功,积分系统根据订单信息查询积分兑换规则,给用户增加积分。
如何判断哪种更合理呢?我一开始觉得都各自有各自的优点: 第一种方式只让积分系统做积分相关的事情,跟各个渠道相关的事情,让渠道自己解决,只负责传入约定好的信息供积分系统做数据查询和更改;第二种看起来也有他合理的地方,各个渠道业务负责自己的部分,在第一种方式的基础上,更进一步的权限让业务自己做积分的增删改查;第三种方式,所有渠道业务的积分规则都内聚在积分系统中(这种现在忽然觉得不是很好,积分系统既要维护积分相关的事情,还要维护渠道相关的规则)。
如果一个功能的修改和添加,经常要跨团队、跨项目、跨系统才能完成,说明模块划分不够合理,职责不清晰,耦合严重。
为了避免业务知识的耦合,让下层更加通用,我们一般不希望下层系统包含太多上层系统,但是可以接受上层包含部分下层系统的业务信息。比如,订单系统、优惠券系统、换购商城等作为调用积分系统的上层系统,可以包含一些积分相关的业务信息。但是,反过来,积分系统中最好不要包含太多跟订单、优惠券、换购等相关的信息。
综上(这不就明说嘛), 更倾向第一第二种。
2.设计模块之间的交互
-
系统之间常见的交换有两种:同步接口调用以及利用消息中间件异步调用。第一种简单直接,第二种解耦效果好。
-
比如用户下单成功后,订单系统推送一条消息到消息中间件,营销系统订阅订单成功消息,触发执行相应的积分兑换逻辑。这样订单系统和营销系统解耦,订单系统不需要知道任何跟积分相关的逻辑,二营销系统也不需要直接跟订单系统交互。
-
上下层系统之间的调用倾向于通过同步接口,同层之间的调用倾向于异步信息调用。
上下层没有互相调用,只有上层调用下层,调用关系简单。同层之间互相调用,错综复杂,使用中间件异步消息解耦,让调用关系简单。
3.设计模块的接口、数据库、业务模型
下文具体展开
业务开发包括哪些工作
- 主要是接口设计、数据库设计和业务模型设计(业务逻辑)。
- 接口和数据库的设计都比较重要,设计完之后改动较少,因为改动起来设计的模块较多,effort比较大。而业务逻辑侧重内部实现,不涉及外部依赖,也不包含持久化数据,对改动容忍度更大。
数据库
我们只需要一张记录积分流水明细的表即可:
接口
接口要符合单一职责原则,粒度越小通用性越好。但是随之而来的问题是:
- 如果接口调用走网络(也别是公网),多次远程接口调用会影响性能;
- 另一方面,一个原子操作被拆分成小接口,可能会涉及分布式事物的数据一致性问题。
为了兼顾易用性和形象,我们可以借鉴facade模式,在职责单一的细粒度节口之上在封装一层粗粒度的接口给外部。
业务模型的设计
- 从代码实现来说,大部分业务系统的开发都可以分为Controller、Service、Repository三层。Controller负责接口暴露,Repository负责数据读写,Service曾负责核心业务逻辑。
- 除此之外还有两种开发模式,贫血和充血模型,这里的积分系统比较简单,使用贫血模型即可。
- 从开发的角度我们即可已将他作为一个独立的项目,也可以跟其他业务代码放到用一个项目中开发。运维的角度我们可以和其他业务一起部署,也可将其作为微服务单独部署。
- 因为其较为简单。我们可以将他跟营销吸引放到一个项目中开发部署。
为什么要分MVC三层开发
- 分层能起到代码复用的作用。同一个Repository可能会被多个Service调用,同一个Service可能会被多个Controller调用。比如,UserService 中的 getUserById() 接口封装了通过 ID 获取用户信息的逻辑,这部分逻辑可能会被 UserController 和 AdminController 等多个 Controller 使用。如果没有 Service 层,每个 Controller 都要重复实现这部分逻辑,显然会违反 DRY 原则。
- 分层可以起到隔离变化的作用。基于接口而非实现编程。每一层的实现可能随着迭代变化,但是其他的层级不需要关心,因为层与层之间的接口不会变化。比如底层数据库要从MySQL变为Oracle,只需改动Repository层的代码即可。
- 分层可以起到隔离关注点的作用。分层之后职责分明,更符合SRP,代码内聚性更好。Repository只关注数据读写,Service关注业务逻辑,Controller与外界打交道,数据校验、封装、格式转换,关系业务逻辑。
- 分层可以提高可测性。单测的时候分层可以让我们很方便的mock其他层的接口。
- 分层可以应对系统的复杂性。大型项目的拆分,水平就是做模块化,垂直就是做分层。
BO、VO、Entity存在的意义是什么
-
每个层级各自定义数据对象
- 每层的数据对象虽然有很多重复的字段,但是也有一些不一样的,比如Userntity中可暴露Password字段但是UserVo中不可以暴露;
- 他们虽然代码重复,但是功能语义不重复。从职责上讲,他们属于为各自的层级服务的数据对象;
- 减少了层级之间的耦合,层级之间用接口交互。
-
解决代码重复问题?
其实他们不违背SRP原则,真的要减少重复,可以通过为他们定义一个父类包含公共字段,因为层次浅,不太复杂不会影响可读性和可维护性。
或者可以通过组合的方式,抽离出来公共字段在一个公共类中,VO、BO、Entity 通过组合关系来复用这个类的代码。
上述方法属于为了减少而减少重复,没有特别大的意义。
-
不同层级之间的数据如何传递和转换?
- 手动复制。找一个mapping关系赋值。
- 但是存在一个问题是,会给每一层的Objects的属性都设置了setter方法,可能会导致数据被随意修改,违反OOP。-> 不太需要担心,VO、BO、Entity 都是只负责本层,而Repository和Controller层又不包含很多业务逻辑所以相对安全。Service层的话危险一点,但是设计本身就是一个权衡利弊的过程。