开篇有话说:
2018年伊始,从东方航空iOS客户端7.0+ 版本开始,我便着手参与了其漫长的重构之路,实践之路总是令人亦喜亦忧,好在功夫不负有心人,呈现给了用户更加流畅的使用体验。期间也总结了一些经验,现在想来系统回顾一下在这个过程当中我和我的团队所遇到的一些值得记录的问题。
首先,当我们开始讨论客户端应用架构的时候,我们最终在讨论些什么?我没有写过java,对于Android客户端不是很了解,下面的讨论会专注于iOS应用方面,但是我想无论是什么平台,架构思想应该是相通的,只是实现手段不同罢了。
一般来说,大部分应用实际做的事情如上图所示。简单说就是调用API,展示页面,然后跳转到别的地方再调API,再展示页面。
确实,App就是为了做这些事情而诞生的,那么支撑这些事情的基础包含哪些呢?这就是做应用架构要考虑的事情。
调用网络API
展示页面
数据的本地持久化
动态部署方案
基于上面四点来细说一下架构考虑解决的问题:
如何让业务开发工程师方便安全的调用网络API?如何尽可能保证用户在各种网络环境下都能有良好的体验?
页面如何组织才能尽可能降低业务方代码的耦合度,降低业务方开发页面的复杂度,提高他们的开发效率?
当数据需要做本地缓存时,如何优化数据在本地的存储方式来尽可能的降低性能损耗?
紧急情况下,如何避开iOS的审核周期,在不发新版本的情况下展示新的内容给用户,如何紧急修复bug?
上面几点是针对App来说的,那么针对开发团队我们还需要考虑很多,例如:
高效收集用户信息,给产品和运营提供数据参考;
合理组织各个业务方开发的业务模块,以及相关的基础模块;
每天都会进行的App代码管理打包,提供给QA的测试工具;
实际上要考虑的远不止这些,还取决于App的规模复杂程度以及公司对App的定位和迭代规划。
所以,当我们讨论客户端应用架构的时候,我们差不多都是在讨论这些问题。
上面细分出来的问题应该会在后续文章中来写,那这篇文章就是来说说一些通识,也方便大家一起讨论,对于这系列的问题有一个初步的印象。
应用架构设计的方法
万事开头难,当我们开始着手设计并实现一层架构甚至设计整个App的架构时,很多时候都感到任务艰巨如履薄冰,甚至有种无从下手的尴尬,因为每一个架构师都会有自己的一套解决问题的思路,但是不管你采用什么样的方法,高度的代码审美能力和全局观,以及灵活使用各种设计模式都是贯穿其中的。
下面分几步来说说如何一步步进入设计之路:
(1)明确解决哪些问题,找到解决这些问题的充要条件。
要很清楚业务方需要什么,肯定不是为了造轮子体验新的技术而修改架构方案,比如以前用的是MVC,后来又流行MVVM,那如果过去MVC是个好的架构,没有太大的缺陷,就不应该一棒子推倒打死搞成MVVM,毕竟适合自己的才是最好的嘛。
搞清楚充要条件很重要,这将决定你的架构是否易用,提供对外的API传的参数越少越集约,耦合度相对而言也就越低,那么模块维护与升级的开销也就越低。
(2)问题应该分类,分模块
这一点无论是不是搞架构的工程师,必定都会举双手赞同吧,不多解释了。
(3)理清各种问题之间的依赖关系,做好注释,建立好模块之间的交流规范并设计模块;
毕竟架构往往不只是一个工程师开发和维护的,在软件的迭代过程中,会有各种需求把不同模块的开发者联系到一起,如果没有条理,模块是使用没有规范的依据,可想而知那将会是一场代码灾难,没有一个工程师想来维护半天都看不明白的烂代码,你懂的。
(4)童鞋,你可能不仅要考虑现在还要考虑未来,对未来可能的走向要做必要的预测,以便降低兼容新模块的成本;
一个好的架构绝对不是一劳永逸的工程,软件是有生命的,你做出来的架构将决定它的一生是幸福还是坎坷。
(5)先实现基本模块,解决最基本的需求,再用基本模块堆叠出整个架构;
这个就很好的体现了你设计架构的逻辑之美,基本模块一定要严要求高标准,在后续的扩展过程中发现问题及时调整,否则随着业务逻辑的扩张所带来的维护成本很可能消磨掉你所有的野心。
那说了这么多,到底啥样的架构才是好架构呢?
我默默的列出了一串标准:
代码整齐,分类分层明确,没有common,没有core;
不用文档或者少用文档就能让业务方上手;
思路和方法要统一,函数名易理解,不玩捉猫猫;
少的横向依赖,万不得已不出现跨层访问;
接口少,接口参数少;
易测试,易扩展;
高性能;
这个是我按照自己的经验根据重要性来排的,当然没提到的并不代表不重要,还要看业务方的具体需求。
补充:
(1)关于架构分层
上面提到好的架构标准当中有提到分层明确,被我一笔带过,我想就这点多说一些:
其实分层这个概念由来已久,我们常见的分层架构有三层架构的:展示层,业务层,数据层;也有四层架构的:展示层,业务层,网络层,本地数据层。这里所说的三层四层跟TCP/IP的五层或者七层协议有所不同,就是说你根据自己的项目架构在逻辑上分为几层那就是几层,具体叫什么做什么没有特定的规范。
像网上常见的MVC架构,MVVM架构,这种层次划分主要是针对数据流动方向而言的,在实际设计当中,针对数据流动方向做的设计和针对模块分类做的设计会放在一起,就是说一个MVC架构可以是四层:展示层,业务层,网络层,本地数据层。
那么为什么要强调这个呢?因为几年前,业界就很流行三层架构的说法,然后出现了各种文档解说三层架构并且喜欢把它和MVC放在一起说,以至于很多人会认为MVC就是三层架构,其实不是这样的。三层架构里面其实没有Controller的概念,而且三层架构的侧重点是模块之间的逻辑关系,MVC有controller的概念,它描述的侧重点在于数据的流动方向。
那么问题又来了:为什么是三层架构流行了起来,而不是四层五层架构呢?
因为不管什么样的功能模块,角色只有三种:数据生产者,数据加工者,数据展示者,简单来说软件只会有三层,每一层扮演一个角色,替他四层五层一般都是从这三层里面的一层中分出来的,所以用三层架构来描述比较普遍。
那我们如何来做分层呢?
严格来说分层不是在架构一开始的时候就考虑的问题,虽然一般我们会按照自顶向下的方式来设计架构,但一般情况下不会上来就约定三层架构或者四层架构,首先确定的是基础模块的划分,之后再细分模块,分类之后一般大多是三层,如果发现某一层特别复杂庞大,那就可以考虑再进行拆分变成四层或者五层。
举个例子:要设计一个即时通讯的服务端架构,怎么来分层呢?
不要一上来就往三层架构上套,这样是设计不出好的架构的。
我们先来列举一下要解决的问题:
1. 要完成用户登录退出的逻辑;
2. 解决不同用户间数据交流的问题;
3. 解决用户数据存储的问题;
4. 如果是多台服务器的集群,要解决用户连接的寻址问题;
解决1需要一个链接管理模块,可以通过链接池来实现,解决2需要一个数据交换模块,解决3需要有一个数据库,解决4有多种方案,这里我们简单处理需要一个寻路模块;
于是我们就有了这几个模块:链接管理,数据交换,数据库,寻路模块。
当然到这里还远远没有结束,还需要针对不同的情况对这四个模块进行细分,直到足够小足够解决具体问题为止,这里就不深究了。
其实到这里还是跟几层架构扯不上关系,当所有的模块都分出来之后,你就需要开始整理这些模块,简单的架构图可能是这样:
然后你看一下图,1,2,3,嗯一共三层,所以那就是三层架构啦,其实这个过程的关键点在于:找出所有需要的模块,并把模块放在该放的地方。
这个例子侧重点只在如何分层,至于其他必要的东西比如数据采集,性能优化等等都没有放进去,看到这里,相信你因该对架构如何分层有了一个具象的理解了,是的你没猜错:答案就是没有分层!所谓分层已经是架构图出来之后的事情了,所以你看别的架构师在分享时上来就说这个架构分一下几层…之类的话,那都是人家架构做好之后的提炼,绝不是上来就用分层来左右了架构的搭建。
其实实际的iOS开发过程中,对于客户端的架构苹果已经帮我们做了绝大多数的事情,至于如何站上巨人的肩膀去眺望远方,那就是你自己的事情了。
(2)关于没有common,没有core
为什么我不建议大家创建common,core这样的文件夹呢,还是有必要解释一下。
一般情况下,很多项目管理文件的时候都有一些存放项目公共类的文件夹,比如处理image,一般这些文件就.h和.m两个文件,单独做一个模块觉得没必要,于是乎大家就经常把它们放到了common里面,当时看来并没有什么问题呀,但是随着版本迭代,模块扩充增大,业务越来越复杂,后续维护的工程师在需要扩张这些小的模块时,不太会去考虑横向依赖问题,因为这些模块都在common里面,直接进行互相依赖是符合直觉的,也不算破坏规范,于是这里就成了common代码混乱的罪魁,当common里面的依赖关系越来越复杂,再想把它们一出来单独做成一个模块,已经是爱莫能助。而且使用Cocoapods来管理的项目。Common往往就是一个pod,很容易引起混淆。
对于一些体量巨大的App来说,common本身就是一个粒度很大的模块,如果不断的把它当做中转站,到后来基本没人敢动了,为什么没有人敢整理这的common呢?
1. 原来大家都是用的好好的,烂就烂点,你改了,你能拍胸脯保证不出错嘛;
2.这么复杂的东西短时间肯定搞不好,任务重耗时长,你的leader会同意你把眼前的功能延迟来特么搞很可能炸掉的旧代码嘛;
3. 就算你千辛万苦搞完了,QA肯定要再做一遍回归测试,任务辣么大,你们说服他们配合你工作嘛;
吃力不讨好,这就尴尬了。所以不建议开common、core这样的文件夹为这些很可能出现的问题留下后患。再小的模块再小的代码我建议也把它们拎出来单独做模块,最多就是多写几行代码,但相比消除common、core所带来的隐患,还是很值的。