前言
本文档的目的是为了让研发人员在写代码之前能够进行系统化的全面思考,做到在动手之前心中已经有了较为全面的方案,并使后续的业务填充和局部设计能够在既定的架构框架内规范演化。Feature
动手开发之前、项目
/模块
重构优化之前,都应该掌握本规范的思想并以之为设计指导。
架构设计目的
- 确定系统边界,确定系统在技术层面上的做与不做
- 确定系统内模块之间的关系,确定模块之间的依赖关系及模块的宏观输入与输出
- 确定指导后续设计与演化的原则,使后续的子系统或模块设计在规定的框架内继续演化
- 确定非功能性需求,非功能性需求是指安全性、可用性、可扩展性等
架构合理性评估
我们不需要也不应该过度设计,更不应该使架构设计脱离业务,设计的再好无法落地也没有任何价值。
那么如何确定架构设计是否合理是否OK了呢?至少应该满足以下几点:
- 【可落地】从业务到技术的映射点足够明确。
所有业务需求都能被转化为技术实施。
- 【可扩展】从业务的扩展点到技术的扩展点映射足够明确。
业务上增加某个新功能时能立即想到应该在哪个技术点/模块上进行迭代。
- 【可迭代】业务上的某一些功能做到极致,即是在架构上对某些模块的算法/策略做迭代优化。
因此我们必须掌握核心技术的迭代优化权力,不轻易假手他人
- 【高内聚、低耦合】任何业务功能增加都能够被映射在技术架构的某个局部范围内,而不是离散修改。
- 【高撸棒性、高性能】设计上没有明显的性能短板,能满足未来一定产品阶段(以年计)的需求。
架构设计规范
思考要点(有序)
【业务层面】先对业务进行抽象和模块化思考
- 【业务宏观】待设计的业务功能(模块)在整个业务系统中所处的位置在哪?
- 了解业务宏观有助于思考更合适的方案
- 【业务结构】它在整个业务系统中的上下游是什么?是否有外部依赖?如何依赖?
- 【业务模块】业务场景上是否存在可替换性?是否应该独立迭代?
- 近期能力如何?远期又可能如何?
- 【业务模块】业务场景上是否存在平行同类业务?他们是否有共性?差异点如何?
- 他们在功能场景上是否各自独立?
- 他们是否应该独立发布、独立控制?
- 【业务模块】待设计的业务功能(模块)是否需要独立发布、独立控制?
【技术层面】基于以上业务理解进行相关的技术设计思考
- 【宏观】上下游依赖的数据结构如何?协议如何?
协议的扩展性、兼容性
如何?是否需要自定义协议来满足?
- 【宏观】如何设计顶层接口,以满足业务可替换性?
- 如何满足
独立迭代
需求?是异构服务接入还是工厂化配置? 当前的最优
实现方案是什么?未来的最优
可能是什么?- 什么样的顶层接口,才能够满足他们之间
平滑迭代
?
- 如何满足
- 【宏观】平行同类业务的功能如何实现共性复用?
- 如何满足
里氏代换
原则? - 如何进行包装设计?
- 如何满足
- 【模块】业务层面、系统控制层面分别应该具备那些(接口)功能?
- 它们如何被使用者调用?
- 如何对它们进行
模块划分
? - 如何进行
功能隔离
? - 如何进行数据
环境隔离
? 扩展点
在哪里预留?
- 【流程】所有实例对象的
生命周期
如何规划?- 入口点是谁?
- 谁负责创建谁?
- 何时对实例进行回收?
- 【细节】哪些对象 / 模块需要设计为单例?
- 【细节】哪些对象 / 模块需要满足线程安全?如何设计?
- 【细节】哪些对象 / 模块需要满足高性能 / 高可用?
规范要点
- 函数/类/包/模块/系统/子系统在设计时,必须明确其功能边界和上下游依赖关系。
必要时辅以注释或 package-info.java 进行规定。
- 顶层设计:系统/子系统模块必须明确交互协议,并考虑其在客户端/云端调用的易用性、安全性。
- 顶层设计:包/模块的功能是泛指业务功能和系统流程功能(非业务性质),禁止过多关注细节。
- 内部设计:函数/类的功能是指具体明确的功能,必须尽可能的确定细节。
- 函数/类/包/模块在设计时,必须明确 I/O。
- 拆顶层模块时,只需要明确接口类型(或抽象类型)。
- 拆分内部子模块时,需要注意将非业务和业务数据隔离。
- 所有I/O传输的数据类型都应该可被序列化(只能为POJO ,禁止包含BO)。
- 函数/类/包/模块在设计时,必须遵循开闭原则(对于扩展是开放的,但对于修改是封闭的)。
可使用
继承方式
和Wrapper包装
聚合方式实现扩展。
在使用继承方式时,必须满足里氏代换原则
。 - 任何类/包/模块的设计都必须尽可能满足高内聚、低耦合原则。
最小化暴露访问权限,写权限收敛。
- 不设计Util工具类或谨慎定义Util工具类并控制其包可见范围。
- 明确
区分主线逻辑和支线逻辑
,两者必须不能互相影响。- 主线逻辑:业务 / 系统运转的必要逻辑流程。
- 支线逻辑:为辅助主线逻辑而开辟的支线逻辑流程(例如:定时读取/写入一些配置)。
- 在外调场景下(查询缓存 / 数据库 / 三方服务),严格
控制 QPS 放大
问题。 - 在海量数据处理场景下,必须注意
数据倾斜
问题。reduceByKey 永远优于 groupBy。
数据倾斜:在分布式计算时由于部分Key数量巨大导致计算节点出现负载严重不均衡的问题。 - 为数据库设计
合理的主键和索引
。- 主键必须满足业务唯一性。
- 主键满足散列分布,以提高数据库集群的存储均衡和查询负载均衡。
不同的业务对散列的需求程度不同,散列算法没有银弹(如LBS业务更偏向地理栅格化读写)。
- 索引的数量控制在
4
个以内。 - 慎重设置多字段关联索引,除非它们总是同时同序出现。
保证Data型(POJO)的类设计足够简单
,不应该引用非Data型的对象。- 最大化保证属性的常量化,和写权限收敛。
- 为需要在HashTable中使用的数据设计合理的hashcode和equals。
- 为需要进行排序和比较的类设计 Comparable
实现。 - 为需要迭代的类设计 Iterable
实现。 - 为可互相转换的类型定义合适的转换函数。
有参转换通常以静态 ofXXX / fromXXX 命名;无参转换通常以非静态 toXXX命名
- 为业务和系统设计合理的配置属性类(Prop)对象,并使其构造常量化。
- 配置类应为简单的POJO类对象。
- 配置类应在系统启动后第一时间全部初始化构建,且
只构建一次
。 - 配置类属性在赋值后就
不应该再被修改
。 不应该在Data型中引用
,只在业务型(BO)中按需注入。
- 为业务型(BO)的类设计合理的功能,而
避免Util的封装
。- 保证BO功能执行的撸棒性,对异常要有全面的考虑。
- 严格控制访问/继承/覆写权限,保证任何可访问/可继承/可覆写的内容都不能对业务造成影响。
- 为可替换的功能部分设计合理的接口和注入方案(配置/工厂/反射等)。
- 为模块/子系统/系统设计合适的安全保障策略。
- 健康检测监控打点。
- 权限校验和数据加密等安全策略。
- 熔断/降级/流量控制等撸棒性策略。
- 尽可能不重复造轮子,复用成熟技术栈,提高效率。
造轮子的前提:
- 既有的不可用、不易用、契合业务难度大。
- 属于业务核心技术的组件,必须自己造轮子(或掌握迭代优化权在业务团队手中)。
设计文档规范
- 逻辑合理:所描述的
逻辑自洽
,前后策略思想不能出现矛盾的地方。 - 考虑全面:内容需要
考虑周全
,勿遗漏
一些边界条件
。模块需要考虑客户端、云端,及交互协议
。 - 简明易懂:
充分考虑文档受众的感受
,站在他人角度思考,如何让他人高效、直接的看懂设计文档。 - 以图说明:
善用各种架构图
,逻辑架构图、部署架构图、开发架构图(顶层图/模块图)、时序图、数据流向图等来说明。 - 测试办法:描述测试人员如何进行测试等相关办法和方案,后续则基于此输出
测试计划
。 - 发布办法:新代码的部署/发布办法(含灰度)应该在文档中体现,后续则应基于此输出
上线计划
。 - 运维办法:描述在新代码发布以后,相关人员如何进行
安全监测和维护
。