软件规模扩张与其组织粒度的进化
概述
软件规模的扩张触发软件本身组织的进化,组织的粒度从基本数据类型的组合到类,模块,服务,平台,云端应用等,抽象与重用的级别越来越高,粒度越来越大,距离越来越远。
组织结构进化促进团队做更好的分工,实现更大规模的系统,系统集。
1:软件规模扩张与组织进化
软件规模随着软件要解决的问题规模的扩张而不断扩大,软件规模越大就需要越多层级的组织。即要解决软件内部功能的分工与重用,也方便软件研发团队的分工。
分工的手段就是拆分,或拆分的目的是为了更好的分工,规模扩大后各个处理间的距离差距就越大,或耦合性不一致,按耦合度分工,将耦合紧密的分工到一起。在不同层级上按耦合度进行多级的拆分。
规模越大越有更大粒度的封装,应对更大规模的分工。粒度越大重用的距离也越远。
1:数据封装与处理封装
结构体将基本数据类型组织起来提供高级别的数据类型抽象。
函数封装处理逻辑,横向上拆分出重复的一些处理作为基础函数。
2:状态与行为封装
数据结构与处理逻辑是有关系的,可将数据结构与处理结合抽象出某些业务对象的状态与行为。--类与对象。
3:模块化与分层
垂直方向按业务拆分为多个模块,负责不同的业务,由不同团队维护
水平方向按处理流程拆分包括常见的的control,service ,db分层
拆分为多种模块后模块间以api方式调用,屏蔽内部实现。
4:服务化拆分
将软件在物理上按水平与垂直逻辑进行拆分,拆分后相互间在物理上是分开的,
代码,编译后静态成果,运行 都分开了。
在比模块更大粒度上的封装。
2 :拆分的问题与手法
2.1 原则与手法
拆分核心原则:耦合度
将关系紧密的逻辑放到一起,不紧密的拆开,拆分后相互间只保留必要的交互接口。
这样也方便拆分后各自独立的变更。独立发布。
拆分手法:水平,垂直。
水平按业务,垂直按处理阶段。
2.2 拆分后的问题
拆分后要解决的主要问题
拆分后如何使用(重用的方式)
拆分后的依赖管理-合理拆分
拆分后隔离-避免职责重叠
拆分后的版本管理-独自发展并避免版本不兼容导致冲突问题
2.2.1 拆分后如何调用
数据结构函数对象都是可以相互直接使用的。
模块是本地接口的调用
服务化是远程调用
2.2.2 拆分后的依赖问题
2.2.2.1 依赖设计原则
单向依赖,只依赖需要的依赖。
水平方向的拆分依赖按层次,高层依赖底层
横向拆分也有配合,也存在依赖。
合理划分依赖层级,避免出现循环依赖
只依赖需要的依赖 。
组件设计的原则(目的跟依赖设计类似,参考)
内聚原则:
哪些应该在一起。功能完整,又不庞大
1:复用发布等同原则
组件是软件复用与发布的最小粒度的软件单元
2:共同封闭原则
为了共同目的共同修改的放到一个组件,不同目标修改的放到不同组件
变更最好在一个组件中,不要一个修改要改多个组件
3: 共同复用原则
不要强迫用户依赖他们不需要的东西
耦合原则:
无循环依赖原则
稳定依赖原则
依赖关系必须执行更稳定的方向。组件不应依赖一个比自己不稳定的组件
3: 稳定抽象原则
稳定的组件应该是抽象的,不稳定的组件是具体的
比如:组件是具体的不稳定,可以以这个组件对外提供一组接口。把接口封装 在一个专门的组件
中,这个接口组件比较稳定,抽象。
2.2.2.2 各种粒度的拆分依赖
数据结构与算法
本身就是细粒度的,依赖的都是需要的依赖。
类与接口
类存在层次结构,如果只需要依赖基类,就不要依赖子类。
接口:行为的集合,也存在层次关系,如果只需依赖小范围的行为集合-就用小接口拆分,
依赖小接口就够就不要依赖大接口。
尽量用组合替代继承,用接口替代类。组合比继承依赖更轻,接口比类依赖更灵活。
服务
服务的合理分层,隔离依赖,只依赖下层服务。服务调用尽量不跨层。
避免服务的循环依赖。
服务间可通过消息方式隔离直接依赖。
如果一个服务依赖底层的多个服务并且每个服务只依赖其一部分,思考是否有某些共通的抽象,将这些依赖放到一层,隔离底层的多个服务。
依赖关系出现问题也许意味着服务设计出了问题。
2.2.2.3 依赖在物理上的变更
依赖在程序物理文件上粒度越大,距离越远
1:同一个物理文件内的依赖
项目工程内,代码间,类,模块间 相互引用。
c系列,go语言 可将依赖的其他团队的代码编译到同一个文件。
2:不同物理文件的依赖
静态库或动态库依赖:c,c++ 开发软件使用安装到系统上静态库或动态 库文件。
多个软件可依赖相同的库文件,共享+函数级别
包:java 通过jar包 将依赖包放入相关路径,动态加载。
3:不同机器上的依赖
服务化后,依赖的服务运行在其他机器上。
4:编译的选择
依赖要不要编译进同一个文件
依赖编译到同一个物理文件
缺点:程序加载的时候整体加载,加载的程序文件大,不同程序间不能共享
编译后不能局部替换。
优点:加载后运行快,编译成功后运行时不用考虑版本冲突问题。
依赖在不同程序文件
依赖在不同文件好处
加载的时候可以动态加载,不用启动全加载。
可以局部升级,不升级程序只升级依赖文件
缺点:局部升级是把双刃剑,可能导致版本不兼容
具体分为两种共享库与私有库两种形式
1>共享库模式
优点
可以多个程序共享相同的库,物理上节省,不用重复安装。
库升级时可以升级一次多个程序的依赖都升级。
缺点
升级可能导致某个程序版本不兼容,运行故障。
2>私有库
java 的jar包依赖 私有文件库依赖
程序依赖的jar包只本程序私有
优点:
不会出现某个程序升级依赖导致其他程序版本不兼容
缺点:
包文在不同程序间重复
包内很多文件可能是不需要的。
java与go 编译时依赖处理方式比较
go将依赖编译到同一个程序,java 依赖在不同的jar包。
java
编译时按包的版本名称整体依赖(不精细到代码中是否真的需要--java可动态反射不能这么做),加载的时候是按需加载需要的类(不是jar)。
缺点:依赖jar中有不需要的文件,优点是动态加载方便扩展与升级。
也可以随时替换某个包或包中某个类文件---线上紧急打补丁就可以这么做。
动态加载决定其可以有tomcat这样web服务器,一个进程下挂多个web应用。
-只有物理硬盘上的浪费。
go
编译时进行代码级别的依赖分析,只依赖需要的代码,编译到一个文件。
编译成功后不能进行局部的更新, 不能动态加载,运行时没有版本冲突问题。
2.2.3 拆分后隔离
主要问题
避免出现拆分后职责重叠
相同的全局信息重名
全局重名
比如库级别重用时出现重名的全局变量,或多个地方修改全局变量
类在不同模块或包中类名重复,或出现全局数据冲突
服务出现命名冲突
职责重叠
职责逻辑调整时,需要在多处维护。并容易出现遗漏其中一部分导致不一致。
职责重叠也会导致依赖时出现不唯一问题,需要这部分职责的要依赖谁?
从设计上避免职责重叠。
2.2.4 拆分后的版本管理问题
存在依赖关系的如果被依赖方变更时,依赖方不变更,容易导致版本不兼容问题。
包括静态不兼容-编译失败 与动态不兼容-调用失败。
主要方法
向前兼容
通用版本管理策略
向前兼容
函数--提供多版本,
类-通过继承或面向接口,扩展接口实现版本变更
服务接口上兼容扩展
版本管理
同一个物理文件内的依赖,编译后没有版本冲突问题
不同物理文件的依赖:私有文件,不共享
共享:通过文件版本号等方式唯一确认。有时就是为了方便替换
因此需要在更上层做好版本控制,比如版本号的规则,替换时版本冲突检测等。
服务级依赖
服务可多个版本共存
指定版本控制策略,定期升级。
3:思考与展望
回顾历史审视当前,面向未来。
程序拆分随规模扩张出现更高层级的抽象与重用,更大粒度的拆分。
服务化是是同一个公司内部大规模程序的高级别抽象,未来会出现更高级别的抽象吗?按什么模式组织程序?谁是服务化后的下一站。
比服务更大粒度
平台化,及火热的中台建设是比服务更大一级的抽象。
重用超过公司的范围
云服务:重用其他厂商的云服务,目前是各种基础服务,存储,基础设施,将来出现云应用的重用。
比如支付服务,三方登录,邮件,短信,快递服务都可理解为不同粒度的云应用服务。
将来也许会出现更多商业组织或政府部门将自己的服务以应用级的云服务提供给别人集成,
厂商在构建自己的应用时可直接在市场上匹配相关的应用,作为自己应用的组成部分,而且市场的应用都可以是可动态替换的。
厂商提供的应用依赖的应用可由用户购买时选择组装。有公信力的政府认证服务,IM服务,交易保证
等用户按自己要求组装。
评论