你的技术 leader 不懂这个?没有它就是没有设计的完成思考过程
“ 在光速交付软件功能特性的时候,研发人员通常选择不去全局思考便动手实现,缺乏周边人员对齐,没有对利益相关者进行充分的访谈就开工了。而用例其实是设计的起点,如果连系统用例都不清楚,又何谈实现的是个正确的系统呢。而在我工作的过程中,有的高级工程师对用例 2 字都未听说过,或者选择在心里过一遍用例,而不是边思考边画出来。那么是时候讲讲用例的重要性,以及为何一定要把它画出来了。 ”
以下大部分来自于我的读书笔记,以及个人思考总结。
业务逻辑
什么是业务逻辑?程序中真正用于赚钱或省钱的过程,无论是计算机执行的过程还是人工执行(我认为可以总结为直接创造价值的,而非高可靠,安全,韧性等)。例如银行贷款,让计算机计算利息,还是人用计算器计算不重要。关键业务逻辑通常需要处理数据。这些就是关键业务数据。由于逻辑和数据紧密相关,因此可以放在一个对象处理,即业务实体
用例
有的业务逻辑必须要自动化,而不能靠人工执行,即只有作为自动化的一部分才有意义。本质是关于如何操作一个自动化自动的描述,它定义了:
用户输入
得到的输出
产生输出所需要采取的处理步骤
如上所述,用例控制着业务实体之间的交互方式。
用例不该描述用户界面,即不描述用户和系统间接口,输入流和输出流。
业务实体不知道哪个用例在控制他们(依赖反转原则 DIP),底层的用例需要了解高层实体
例子
原始需求:
运维团队需要高效管控账号,需要将账号的申请自动化,减少人工的参与以提升工作效率。
另一方面研发需要账号登录控制台查看资源,也需要用户密码凭证以让程序操控资源
用户故事示例:
通过用户访谈,提炼用户故事等手段,将上述内容转化为用例视图,我们看下采访后总结的用户故事
作为运维人员,需要控制他人 web console 的使用权限,仅赋予研发只读权限,以保证资源的安全
作为研发人员,需要创建临时账号,以登录到 web console 进行问题定位和跟踪
作为研发人员,需要申请账号,以配置给程序使用,操作系统的 API
用例视图示例:
说明谁要使用系统,以及他们使用系统做什么,是需求分析阶段的输出件。以用例作为设计的驱动,驱动后续的设计,随时回顾自己的设计是否能支撑用例,保证部署视图,逻辑视图,开发视图,运行视图正确性
用例对架构设计和业务展开的支撑
什么是软件架构:
软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,最小化系统的总运营成本。你需要考虑这些事情
开发
部署
运行:架构设计的影响比较小(架构掌控不了代码执行效率),一般都可以通过增加硬件解决(短期,后期基于业务驱动再优化),因为硬件比人力便宜,基于投入产出比,所以才优先把优化重心放在别处。即便如此,架构应该让开发对系统的运行过程一目了然,即用例,功能,必备行为,简化他们对系统的理解,为的是为开发和维护提供帮助
设备无关性:满足开闭原则,高层策略与底层实现细节分离
维护:成本最高,探秘成本是找到新增功能和修复问题的最佳方式和位置。风险成本是上述修改时,总是衍生新的问题。架构的目的是降低两部分成本
软件至于硬件最大的不同是什么?是不断地,快速的变化
软件架构必须支持以下几点(独立性):
系统的用例:架构必须能支持其自身的设计意图,架构必须为其用例提供支持,因此这是架构师首要关注的问题
系统的运行:如果系统每秒处理 1000 个请求,架构的设计就必须能支持这种级别的吞吐和响应时间
系统的维护
系统的开发:康威定律任何一个组织在设计系统时,往往都会复制出一个与该组织内沟通结构相同的系统。这是因为团队要独立的完成工作,所以需要隔离良好,可独立开发的组件。
系统的部署:便捷性,不该依赖成堆的脚本和配置
保留可选项:
实现以上的平衡很困难,比如我们无法预知系统的所有用例,运行条件,开发团队的结构,或者系统的部署要求。即便提前了解,随着演进,需求会不断发生变化。即目标是模糊多变的。
采用一些原则可以有助于解决平衡问题。将系统划分为隔离良好的组件,尽可能的为未来保留尽可能多的可选项
用例解耦:
按层解耦是水平切分,用例则是垂直切分。所以 UI,业务逻辑,数据库等需要按照用例进行解耦。按照变更原因的不同进行垂直切分,就可以持续的加入新的用例,而不影响老的
解耦的模式:
所有这些解耦对架构目标“系统运行”的意义:
分层解耦可以让组件在不同服务器部署(服务数据库的分层隔离,这有点像 SOA,即面向服务的架构)
按用例,比如不同吞吐的用例可以分开,高吞吐,低吞吐,那么对带宽不同诉求的组件也可以分开部署在多个服务器上
总之按用例解耦有利于系统的运行(其实这就是微服务)
开发和部署的独立性:
当你依据用例和水平分层解耦后,自然就支持了独立的开发互不干扰
消除重复的代码前要分清真假重复:
真重复:每次变更都必须应用到所有副本
假重复:不同的演进路径,包括变更理由,迭代节奏
因此当我们打算合并看似相同的用例时,一定要谨慎否则将来拆分会更难。总之不要因为为了减少重复代码而合并用例。同理,当水平分层时可能看到数据库结构或者 API 接口类似就要合并。一定要考虑演进路径
再谈解耦模式:
用例分层的方式很多,比如
源码解耦(单体架构):模块依赖关系,一个模块变更不会导致其他模块变更
二进制解耦(部署):可能是 jar,ddl,Gem,共享库,只是可独立部署的单元
执行单元解耦(微服务架构):网络通信
项目初期难以确认哪种适合自己,甚至随着项目的成熟,所谓最适合的模式会出现变更
将系统解耦推行到“一旦有需要就可以随时转变为服务的程度即可”,这让系统尽量长时间地保持单体结构,以便尽可能保留可选项
因此做到源码解耦就够了,随着项目发展可以随时快速拆分。
设计良好的架构能允许从单体架构开始,逐渐演进为独立的单元,甚至微服务,也允许回退到单体架构。
可悲的故事
从纯客户端转型 BS 架构,技术满脑子的如何构建大规模服务器集群,选型了三层的“架构”(不是真正的架构,只是一种三层部署拓扑,这也是灾难的根因),那么 UI,中间件,数据库都可以分别部署了,假设要添加一个字段,每个层都要改。结果是开发期根本没有大型服务器可用,也没能销售一个需要大规模服务器的系统。所有文件都跑在一台服务器上,维护成本却很高。—草率的决定无谓的将开发成本放大数倍
一个管理租赁车队的本地业务,找来的新架构师认为运作这个小小的业务需要企业级的面向服务的“架构”。如果想在销售记录添加一个联系人要经过复杂的处理过程,而且了测试这个复杂的过程,要运行消息总线等一些列基础服务。— 太草率的采用 SOA 体系的工具,大量人力耗费在实现 SOA 架构本身
成功的故事
坚信产品不应该让用户下载超过一个 jar 的文件,称为“下载即可执行”这一条规则就指导了之后的很多决策。
决策 1: 自研 web 服务器,没有使用开源的,因为编写一个基本功能的 web 服务器非常简单,且延迟了技术决策
决策 2: 避免考虑数据库,采用数据库无关的设计,只需要接口屏蔽。实现内存数据存储哈希表。当系统需要持久化,再次考虑是否用 mysql,最后认为哈希表写入文件更简单,精力继续放在开发新功能上。三个月后得出了一个结论,文件存储就足够好了,完全不需要 mysql(减少 mysql 的引入这就减少了大量的维护成本,表结构,三方件,密码管理,查询问题,数据库服务器,可靠性等与业务价值没有直接关系的成本)。直到有一天一个客户自己需要将数据写入 mysql,他用了一天就实现了 mysql 存储。软件包分发后,没人实际用到 mysql,连写这个 mysql 实现的客户都没有。
在早期,他们在业务逻辑和数据库间画了一条边界线,防止了业务逻辑对数据库产生依赖,使用文件系统做实验,反倒发现是更好的方案,而这也不会造成使用 mysql 的障碍。即通过划清边界,推迟和延后细节性的决策。以节省大量时间、避免大量问题。这就是助益
架构设计的主题:
A use case driven approach 中的观点:系统架构应该为用例提供支持。架构的设计图应该凸显用例。
架构设计和技术手段,框架无关,他们都是手段。而不是架构设计中包含的内容。
架构设计的核心目标:
围绕用例展开设计,脱离框架,执行环境等因素描述用例。在确保用例需要的前提下,尽可能保留自由的技术选型。
框架不是信条:
选择框架要思考如何保护自己(就是用例,团队等等,不要绑定到框架上),保持对用例关注,不要被框架主导设计
用例对单元测试的价值:
针对用例进行单元测试
总结
可以看到用例可以成为你后续很多架构决策的重要依据。是否应该用 SOA 架构,还是微服务架构还是单体架构?
用例的另一个价值
新来的程序员是否能从设计中一眼就看出来系统名称?新人也应该先了解系统的用例,而不是技术实现。先不要考虑技术细节,以后再决定应该怎么做。
版权声明: 本文为 InfoQ 作者【田晓亮】的原创文章。
原文链接:【http://xie.infoq.cn/article/0c01d5eb112ee91f35b653e84】。文章转载请联系作者。
评论