首页 针对业务系统的开发,如何做需求分析和设计(以积分兑换系统为例)
文章
取消

针对业务系统的开发,如何做需求分析和设计(以积分兑换系统为例)

前言

作为一名软件工程师,对自己的定位不能永远是一名代码实现者,而能独立负责一个系统,能端到端开发一个完整的系统才是最该具备的能力,这其中的工作就包括:前期的需求沟通分析、中期的代码设计实现、后期的系统上线维护等。

现在我以一个积分兑换系统的开发实战,展示一个业务系统从需求分析到上线维护的整个开发套路(业务开发中,也会蕴含设计原则、思想和模式)

需求分析

从前面的学习,大致总结了这么一条需求分析的思路:

  1. 多沟通和借鉴,尽量将业务梳理完全
  2. 将业务中体现的功能一个个列出来
  3. 根据功能点,进行面向对象分析、设计和实现

其中关于业务梳理,有多种方法:

  1. 和客户、同事沟通
  2. 通过产品的线框图 or 用户故事等细化业务流程,用户故事 like 单元测试用例,侧重情景化,以用户的角度来体现一个完整的业务操作流程
  3. 借鉴其他优秀的产品,自行分析业务细节

以积分有效期的用户用例来说:

  1. 用户在获取积分的时候,会告知积分的有效期;
  2. 用户在使用积分的时候,会优先使用快过期的积分;
  3. 用户在查询积分明细的时候,会显示积分的有效期和状态(是否过期);
  4. 用户在查询总可用积分的时候,会排除掉过期的积分。

从这几条用户用例也能发现,业务系统的设计其实归根结底就是增删改查

积分系统的具体需求需求如下:

积分赚取渠道 and 兑换规则

积分的赚取渠道包括:下订单、每日签到、评论等。兑换规则如下:

  1. 签到送 10 积分
  2. 按照订单总金额的 10% 兑换成积分

积分消费渠道 and 兑换规则

积分的消费渠道包括:抵扣订单金额、兑换优惠券、积分换购、参与活动(如抽奖)扣积分等。

兑换规则如下:

  1. 10 积分抵扣 1 块钱
  2. 100 积分可以兑换 15 元优惠券

对于积分的有效期:

  1. 在消费积分的时候,优先使用快到期的积分
  2. 积分在到期后作废,不计入总积分
  3. 有效期可根据不同的赚取渠道进行设置,比如签到积分 7 天有效期,评论积分 10 天有效期等(感觉有点麻烦)

积分明细查询

  1. 查询用户的总积分
  2. 赚取积分和消费积分的历史记录

系统设计

面向对象设计聚焦在代码层面(主要是针对类),而系统设计却是聚焦在架构层面(主要是 针对模块)

如何进行系统设计

合理的将功能划分到不同的模块

合理地划分模块也可以做到模块层面的高内聚、低耦合,架构整洁清晰。

对于前面罗列的所有功能点,有下列三种模块划分方法:

  1. 积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护(增删改查),不划分到积分系统中,而是放到更上层的营销系统中。这样积分系统只需要负责增加积分、减少积分、查询积分、查询积分明细等这几个工作。举个例子:用户通过下订单赚取积分。订单系统通过异步发送消息或者同步调用接口的方式,告知营销系统订单交易成功。营销系统根据拿到的订单信息,查询订单对应的积分兑换规则(兑换比例、有效期等),计算得到订单可兑换的积分数量,然后调用积分系统的接口给用户增加积分。
  2. 积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护,分散在各个相关业务系统中,比如订单系统、评论系统、签到系统、换购商城、优惠券系统等。比如:用户下订单成功之后,订单系统根据商品对应的积分兑换比例,计算所能兑换的积分数量,然后直接调用积分系统给用户增加积分。
  3. 所有的功能都划分到积分系统中,包括积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护。比如:用户下订单成功之后,订单系统直接告知积分系统订单交易成功,积分系统根据订单信息查询积分兑换规则,给用户增加积分。

怎么判断哪种模块划分合理呢?

  1. 如果一个功能的修改或添加,经常要跨团队、跨项目、跨系统才能完成,明显说明模块划分不合理
  2. 为了避免不同业务之间的耦合,应该让下层更加通用,一般不希望下层系统包含太多上层系统(调用系统)的业务信息,但是可以接受上层系统包含下层系统的业务信息,如订单系统、优惠券系统、换购商城等作为调用积分系统的上层系统可以包含一些积分相关的业务信息,但是反过来积分系统中最好不要包含太多跟订单、优惠券、换购等相关的信息。

综上所述,第 1、2 种模块划分方式更合适,但是不管选择哪一种,积分系统负责的工作都是:积分的增、减、查询,和积分明细的记录和查询

设计模块与模块之间的交互关系

在面向对象设计中,类设计好之后,我们需要设计类之间的交互关系。类比到系统设计,系统职责划分好之后,接下来就是设计系统之间的交互,也就是确定有哪些系统跟积分系统之间有交互以及如何进行交互。

常见的系统交互方式有两种:

  1. 同步接口调用(简单直接)
  2. 利用消息中间件异步调用(解耦效果好)

一般,上下层系统之间的调用倾向于通过同步接口,同层之间的调用倾向于异步消息调用。比如,营销系统和积分系统是上下层关系,它们之间就比较推荐使用同步接口调用。同层之间往往不归属同一事务,因而通常不会要求事务一致性,而上下层关系的系统通常属于同一层业务系统,因而同步接口调用更便于控制事务一致性。

如何设计模块本身

业务系统本身的设计无外乎:接口设计、数据库设计和业务模型设计(即业务逻辑)。

针对积分系统,如何设计数据库

它实际只需要一张记录积分流水明细的表就可以了,表中记录积分的赚取和消费流水,用户积分的各种统计数据,比如总积分,总可用积分等,都可以通过这张表来计算得到。

针对积分系统,如何设计接口

接口设计要符合单一职责原则,粒度越小通用性就越好。但是,接口粒度太小也会带来一些问题。比如,一个功能的实现要调用多个小接口,一方面如果接口调用走网络(特别是公网),多次远程接口调用会影响性能;另一方面,本该在一个接口中完成的原子操作,现在分拆成多个小接口来完成,就可能会涉及分布式事务的数据一致性问题(一个接口执行成功了,但另一个接口执行失败了)。所以,为了兼顾易用性和性能,我们可以借鉴facade(外观)设计模式,在职责单一的细粒度接口之上,再封装一层粗粒度的接口给外部使用。(简单来说就是避免多次(远程)接口调用)

对于积分系统来说,我们需要设计如下这样几个接口:

业务模型设计

由于积分系统相对简单,因此选择基于贫血模型的 MVC 三层架构,Controller 层负责接口暴露,Repository 层负责数据读写,Service 层负责核心业务逻辑,也就是这里说的业务模型。

同时积分系统可以作为一个独立的项目开发,也可跟其他业务代码一起开发(如营销系统),从运维的角度它可以跟其他业务一块部署,也可作为一个微服务独立部署,这里更倾向将它跟营销系统放在同一个项目中开发部署,只要做好代码的模块化和解耦,让积分系统更其他业务代码保持边界,无太多耦合,后期需要拆分成独立的项目开发部署也会很简单

为什么要分MVC三层开发?

分层能起到代码复用的作用

同一个 Repository 可能会被多个 Service 来调用,同一个 Service 可能会被多 Controller 调用。比如,UserService 中的 getUserById() 接口封装了通过ID获取用户信息的逻辑,这部分逻辑可能会被 UserController 和 AdminController 等多个 Controller 使用。如果没有 Service 层,每个 Controller 都要重复实现这部分逻辑,显然会违反 DRY 原则。

分层能起到隔离变化的作用

分层体现了一种抽象和封装的设计思想。比如,Repository 层封装了对数据库访问的操作,提供了抽象的数据访问接口。基于接口而非实现编程的设计思想,Service 层使用 Repository 层提供的接口,并不关心其底层依赖的是哪种具体的数据库。当我们需要替换数据库的时候,比如从 MySQL 到 Oracle,从 Oracle 到 Redis,只需要改动 Repository 层的代码,Service 层的代码完全不需要修改。

除此之外,Controller、Service、Repository 三层代码的稳定程度不同、引起变化的原因不同,所以分成三层来组织代码,能有效地隔离变化。比如,Repository 层基于数据库表,而数据库表改动的可能性很小,所以 Repository 层的代码最稳定,而 Controller 层提供适配给外部使用的接口,代码经常会变动。分层之后,Controller层中代码的频繁改动并不会影响到稳定的 Repository 层。

分层能起到隔离关注点的作用

Repository 层只关注数据的读写。Service 层只关注业务逻辑,不关注数据的来源。Controller 层只关注与外界打交道,数据校验、封装、格式转换,并不关心业务逻辑。三层之间的关注点不同,分层之后,职责分明,更加符合单一职责原则,代码的内聚性更好。

分层能提高代码的可测试性

后面讲单元测试的时候,我们会讲到,单元测试不依赖不可控的外部组件,比如数据库。分层之后,Repsitory 层的代码通过依赖注入的方式供 Service 层使用,当要测试包含核心业务逻辑的 Service 层代码的时候,我们可以用 mock 的数据源替代真实的数据库,注入到 Service 层代码中。代码的可测试性和单元测试我们后面会讲到,这里你稍微了解即可。

分层能应对系统的复杂性

所有的代码都放到一个类中,那这个类的代码就会因为需求的迭代而无限膨胀。我们知道,当一个类或一个函数的代码过多之后,可读性、可维护性就会变差。那我们就要想办法拆分。拆分有垂直和水平两个方向。水平方向基于业务来做拆分,就是模块化;垂直方向基于流程来做拆分,就是这里说的分层。

还是那句话,不管是分层、模块化,还是 OOP、DDD,以及各种设计模式、原则和思想,都是为了应对复杂系统,应对系统的复杂性。对于简单系统来说,其实是发挥不了作用的,就是俗话说的“杀鸡焉用牛刀”。

总结用到的设计原则和思想

本文由作者按照 CC BY 4.0 进行授权

设计原则

微服务技术概述