横看成岭侧成峰,远近高低各不同。为了更好地理解软件系统,我们需要借助于多种图表工具,从不同的视角出发,全方位的理解系统设计。以便在设计阶段足够充分预估系统瓶颈,实施难度,开发工时等,在业务功能上实现良好扩展性,性能上高可靠,高可用。达到数据绝对安全,性能足够优异。支撑业务需求快速迭代。
软件系统可以按如下分层
- Iass: 基础设施软件,包括操作系统(网络,存储,计算),虚拟机,Docker等基础软件。
- Paas 平台即服务: 包括我们日常的消息,缓存,数据库,等中间件。也应该包括框架和 类库。例如ioc,orm,rpc框架; 图片,特殊文件处理的第三方类库等
- Saas 软件即服务: 我们日常的一些应用,无论是2B,2C都属于此。
以下我们将重点分析借助哪些图表工具 可以分析 应用软件系统(Saas层)。这些图表的阅读者 应该是开发者,产品经理,业务架构师,系统架构师,技术管理者等。
用例图
用例图是最清晰,最容易理解的图表,用例图从用户角度出发
要素
- 如何使用我们的系统。如果系统已经完成,可以使用简洁版本的 用户使用手册代替。
- 有哪几种使用流程,哪些应用场景。
- 每种流程需要做哪些事情。
用例图首先需要分析系统有哪些使用人员,可以借助以下问题分析
- 谁将使用该系统的主要功能。
- 谁 将需要该系统的支持以完成其工作。
- 谁将需要维护、管理该系统,以及保持该系统处于工作状态。
例如简单的用户修改资料
用例图因为其简单直白,容易被系统设计者忽略,实际上 对于一个完全未接触系统的人,用例图是最友好,最直白的,可以让小白 快速的了解我们的系统 给哪些人提供了哪些功能。功能之间的联系是什么
用例规约
用例规约是对用例的详细描述,一般包括简要说明、主事件流、备选事件流、前置条件、后置条件和优先级等
用例规约既关注主事件流所描述的成功场景,也关注备选事件流所描述的异常场景,有利于促进系统化思维、发现异常场景、完善系统功能和提高易用性;
后置条件应覆盖所有可能的用例结束后的状态。即后置条件不仅仅是用例成功结束后的状态,还应该包含用例因发生错误而结束后的状态
示例
一般情况可以只描述重要需求,关键用例的用例规约。一般需求可以忽略。(PRD里绝对不能忽略)
用例图和系统页面 如果有 更详细的使用手册,则可以更快速的全面理解系统的功能。例如展示一下我们的系统页面。更加直观和清晰。
只有更好的了解系统提供了哪些功能,有哪几种角色,才能理解 系统为什么这么设计? 某些人之所以会觉得用例图多此一举, 是因为其对系统本身足够了解。但是他们忽略其他人对系统还是完全一片陌生。需要借助用例图,最直白的了解系统
数据模型图
程序=数据结构+算法,软件程序就是对输入数据进行处理,按照一定的算法,输出特定的数据。 数据是程序的核心,也是容易变化的部分,例如最常见的变化: 需要增减字段。
此时需要对数据进行建模,梳理模型之间的关联关系,为的是把关系最紧密的数据 放到一个模型里,独立扩展。 数据模型图描述了 模型之间的关联关系,每个模型有哪些字段 。 三要素 ● 模型 ● 属性 ● 模型间关联关系
数据模型图包括 E-R图,数据库实体图。等。
- E-R图使用中文,不涉及表结构,更简单。
- 数据库模型层: 描述数据库层面的关联关系和表设计, 虽然比ER图更复杂, 但是更全面,适用人群都可以看懂。
- 大多数情况下 我们的设计文档是给产品和研发,技术管理者看的。使用数据库实体图也可以看懂。(看不懂的让他去学)
个人认为设计文档里可以忽略E-R图,直接上数据库实体图。但是这就要求数据库实体图 有充分的文字说明,例如属性注释,关联关系说明等。
E-R 图示例
数据库实体图(数据库 ER 图)
数据库 ER 图的梳理过程
数据库 ER 图,实体图或者 领域模型图的设计非常考验设计经验。需要领域专家根据用例图,用例流程图,反复的需求沟通。不断地推敲以下问题
- 业务的扩展点,变化方向 在哪里,未来计划的演进方向。
- 系统的扩展方向是哪里?如何实现扩展性
- 应该包括哪几个 领域实体。
- 领域模型的边界 应该在哪里。关联关系是 1V1,还是 1对 N 等等。
数据库 ER 图的分析过程 可以使用 DDD 等设计方法 。
流程图
系统设计阶段 只需要覆盖核心和关键的业务流程即可。对于细枝末节的一些简单流程应该忽略。 (有精力和时间可以覆盖非核心流程)
管理流程和 用户流程
以营销系统为例,分为管理流程 和用户流程
- 运营创建了一个营销活动。这个过程是管理流程。这个流程对于性能的要求不高。流量入口不同。
- 用户下单等行为, 触发了某些营销活动,这个流程对于性能要求,安全性要求就很高。
不同流程的关注点不同。例如dubbo的rpc系统,也可以分为 初始化流程 和 方法调用流程
- rpc provider 接口提供者需要初始化时注册接口。rpc consumer需要在初始化时监听接口。
- rpc调用流程 从consumer端到provider 是调用流程。
流程图画法
流程图的画法比较灵活。以下是个人经验和习惯
- 组件思维: 可以描述 组件之间的控制流调用
- 数据思维: 可以描述数据之间的数据流变化。
一般使用方框表示 组件,连线 表示调用方法,动作 或者数据。
组件思维
按照组件思维 设计流程图。要求把系统的组件先抽象出来,每个组件处理哪个步骤。淡化了输入输出, 是简化版的 调用时序图。(时序图描述方法调用层次,更加细化和清晰。)
以下示例是dubbo Provider端 暴露一个服务的 组件调用的控制流变化图
数据思维
按照数据流程图描述,组件之间传递的是 数据,数据流描述了 一个组件或系统的输入 和输出 是什么。 其实大多数业务系统,使用数据流程图不太好划分,因为 一个业务流程内主要是针对两三个模型进行处理。组件之间的输入输出界限并不明显 为此可以 将数据流程图和控制图 合二为一。 方框依然表示 组件,但是连线可以同时包括动作和数据
流程图中的非功能型设计
高可靠 高可用 性能瓶颈,流程图中可以介绍核心读写流程的高可用,高可靠设计; 即数据可靠性如何保证,系统可用性如何保证。性能瓶颈在流程中哪个节点。如何优化等
以下仅供参考
- 数据存在哪里,同步/异步写入,异步写入的一致性保证
- 并发操作如何保证一致性(例如库存?)
- 高并发场景如何提高系统可用性,读流程如何优化,写流程如何优化
- 是否 Stand-by 设计双活。
- 幂等性,重试策略、负载均衡策略
时序图
时序图 是更加详细的系统流程图,一般的系统流程图覆盖了关键的系统组件,关键的数据处理节点,但不会具体到哪一个类,哪一个方法。 时序图要求关注 程序执行时的 控制流,时序的展示更好像是 系统执行时的 方法调用堆栈。
时序图要素
- 方法调用堆栈(核心)
- 分支和循环描述,
- 方法和入参描述
- 主要的一些关键节点注释
时序图示例
可以看到时序图 精确到 某个类 调用了某个类的某个方法,把方法的调用嵌套的深度 和层次 都能展现出来。 配合 重要的 关键节点注释,可以让读者即使不读代码也可以看到整体的方法调用体系。 通过时序图我们可以得到
- 某个类在时序图的哪个位置,负责的工作是什么
- 在当前流程中,某个类上下游的依赖是什么
类图
类图描述了类和类之间的依赖关系(组合,继承,接口)
类图要素
- 继承、实现、组合关联关系
- 类的关键核心方法。(记得加注释,用以描述本类扩展了哪些能力)
下面拿一个Spring ioc容器的类图
类图的箭头关系 描述了 两个类之间是否依赖,集成/组合/接口实现。
什么时候使用类图?
简单地业务流程不需要类图
例如一个接口只有一个实现类,没有复杂继承关系 基本上不用写类图。
复杂的继承体系需要类图
类似于 Spring 等极为复杂的框架,为了实现最大程度的复用和可扩展性。使用了大量的继承、接口实现类 提高高扩展性和可复用性。这个时候如果没有类图,根本无法全面的了解 一个接口,一个类的继承体系。不清楚某个类在继承体系中的位置。
为什么类图不可以归到 流程图里
一般情况下只有流程图和时序图里面能具体精确到某个类。当读者在流程图、时序图里经常看到某个几个类,就会疑问,这几个类有什么关联关系呢?
此时 可以择机是否需要 梳理一个 类图展示依赖关系。
类图和设计模式
设计模式的具体介绍都会使用类图展示。例如工厂模式 的类图
系统架构图
系统架构图为了描述应用内部的组件,模块等。一般情况下分为 全系统架构图,单应用架构图
全系统架构图
业务架构图是从业务逻辑的视角出发,整齐地展现出一个企业各类系统之间的层次和关系
单应用架构图
单应用业务架构图按照层次结构可以分为经典的三层结构:展现层、业务逻辑层和数据层
应用架构图
应用架构图的关注点是 应用 在整个系统的位置。(和上面的全系统 业务架构图类似)。应用架构需要把应用在整个系统的位置描述出来。
以下为优惠券系统的 应用架构图。基本描述了一个应用服务在整个优惠券相关微服务里 所处于的位置
例如 CouponJob服务 负责发券,上层会有各种形式的发券活动关联该服务。兑换码,领券页面领券等等都依赖CouponJob发券服务能力。
应用间依赖关系 最理想的情况是单方向依赖,如果上下游的两个服务存在明显的 循环依赖,此时需要考虑,两个系统是否耦合严重, 两个系统是否实现了类似的功能呢? 是否需要合并为一个服务呢?
是否可以把关联性很强的业务模块放到一个服务里比较合适呢?
应用架构重在描述应用之间的依赖关系,以及应用所处在系统的位置。是上层应用还是底层应用? 设计应用架构图时 不建议把 应用内的模块划分也囊括在应用架构图中,这样会导致架构图过于庞大,不利于理解
一个架构图只需要描述清楚 一个视图即可。(职责尽量单一)
以下为HBase的应用架构图
可以从表中看到
- Zookeeper负责存活性监测等集群管理任务。
- master不负责数据读写,只负责DDL建表等语句,负责RegionServer故障转移流程
- RegionServer负责接收客户端读写数据
- HDFS 作为分布式存储,接收RS读写 (至于RegionServer内部有哪些模块,如何读写 自然借助于其他架构图。每种架构图只是从一个视角描述 系统)
部署架构图
部署架构图重在描述应用在线上部署的情况
部署架构图核心关注点
- 流量从管理端还是用户端过来
- 是否做了鉴权
- 流量从哪几个 nginx 过来,公网还是内网,域名是哪个(公网内网,域名不同)
- 应用是否做了负载均衡,策略是什么
- 是否多机房部署,应用部署在了哪几个机房。
- 机房之间的流量如何路由的; 是否存在跨机房调用
- 是否按照用户维度进行了 Set化
- 是否按照地区维度进行了机房路由
- rpc调用路由规则是什么,在哪里管理路由规则
- 部署机房 是虚拟机,物理机,还是 Docker。 私有云,公有云,还是混合云架构
- 依赖的数据库和应用是否同机房部署?
- 其他MQ,Redis,Es等中间件 机房部署在哪里。是否跨机房等。
以下部署架构描述了 应用在容器上部署,用户请求经过 SLB 负载均衡。静态资源访问,数据库服务部署在RDS。
全链路调用 结构图
以下场景需要梳理全链路上下游依赖图
- 接口参数需要变更,接口实现需要变更。确认不影响上游(一般具体实现细节对上游屏蔽。)
- 下掉接口、迁移到新接口
- 限流,熔断,降级策略需要通知到上游
当出现以上内容时,我们需要感知到哪些上游依赖了我们,在什么业务场景下依赖,重要性,优先级如何统一的梳理出系统 rpc 接口,http接口,mq消费者,共享数据库,等 上下游依赖。 可以以表格的形式,例如 对于项目内部的方法调用,模块依赖,我们可以快速的梳理清楚上游的依赖。例如使用IDE 的快捷键。但是微服务下,需要获取调用链路图,只能依赖于 服务治理框架和代码扫描工具 提供的能力,梳理每一个接口上游的依赖。
梳理后可能发现需要 对接口进行鉴权,防止任意调用方可以调用我们的服务。至少可以让我们感知到接口被调用,防止大流量,不合理的业务场景 进行调用。也方便我们日后升级。
每一种架构图都是一个独特的视角
架构图的视角
- 用例图: 用户,产品经理,测试人员 可以直观,清晰的了解我们的系统使用场景,能做什么?
- 数据库实体图: 业务架构师,业务专家,产品经理 更清晰了解 建模的过程,是否存在领域划分不清晰,领域耦合问题
- 流程图: 给开发工程师,业务架构师,业务专家 更清晰的 了解 核心流程 ,数据如何在组件之间流转的,各组件如何调用的
- 系统架构图: 开发工程师 可以借助于此快速了解 系统内 共哪几个模块,如何进行分层的。各层职责。
- 应用架构图: 业务架构师可以看到 微服务之间是否存在耦合,是否依赖不合理,界限是否清晰。开发工程师可以了解负责的系统处于 总体架构的哪个位置。
- 应用部署架构: 业务架构师,开发工程师 ,以及运维工程师 更清晰的了解 服务部署环境, 流量入口,负载均衡策略,路由策略,中间件部署情况等
以上我们分析了设计文档应该包括哪些具体的 图表,此外
设计文档基本原则
设计文档的编写 应该遵循以下基本原则
- 文档是给人看的。 要给目标读者 讲的足够透彻,每个读者的视角都要考虑到
- 可落地。要求文档具备足够的设计细节,能准确预判技术难点,对于技术难点提出明确的解决方案(要覆盖关键需求和流程)
- 一个架构图只需要描述清楚 一个视图即可。追求大而全的架构图只会让人看不懂,自己也无法再修改。(职责尽量单一)