欢迎光临
专注android技术,聚焦行业精粹,我们一直在努力

架构学习3——架构的目的

1、题纲

本文章会学习架构的目的是什么?不了解架构的真正目的影响会比较大,大到公司的项目发布后持续报出系统问题,或者工程项目迟迟无法按时交付;小到架构师常常陷于方案选型的争论,无法定夺。了解了架构的真实目的,这些问题都会迎刃而解。

从上一篇关于架构设计的历史演进我们可以总结出,架构设计的演进史其实就是随着软件设计复杂度越来越高而不断的提出新的合适的改进方案的演进史。所以我们架构的真正目的是:为了解决软件系统复杂度带来的问题

 

2、架构设计的误区

2.1、为了架构而架构

经常听到这样的话:“这个系统很重要,所以要做架构设计”,“粗糙的设计同行同学嘲笑”;或者很多程序员想展现自己的平生所学,想自己的设计鹤立鸡群。

其实我觉得出现上面的现象主要是因为很多人在架构设计上舍本逐末了。其实还是没有理解架构设计的真实目的。大到软件设计的演进历程,小到一家互联网创业公司的系统演进,其实是一样一样的。我记得我刚来公司时我们的软件系统架构非常简单,我们是一个android app,系统架构就是客户端壳+H5展现,但足以支撑当时的业务需求了:我们只需提供软件发布和下载功能。随着业务的发展,app首页运营业务也变得复杂,普通H5页面无法满足用户的交互体验,于是我们开始有了端页面管理,网络模块,崩溃收集模块;接下来随着业务领域不再局限于发布和下载,我们需要留住用户,于是增加了更多的业务模块,例如:个人中心,论坛评论模块等,于是我们有了自己的分模块框架。这也说明了一点:整个的app的系统架构不是一蹴而就,而是随着业务的复杂度提升而演进,业务发展的速度决定了软件架构的演进速度。下面一张图描述了一款android app的软件架构演进史:

从上面的一款app的软件架构演进历程,我们看得出来一个系统架构的演进史是需求驱动的,而非自己臆想出来的。也说明了架构设计并不是一气呵成,而是随着业务复杂度的增加而不断演进的。

2.2、盲目的复制其它产品架构

很多时候我们的产品经理提出的需求在市场上都有对应的竞品,例如产品提出来我们要做一款聊天软件,那么这样的竞品太多了:微信,QQ,钉钉等,那么我们一开始的设计就参考微信的来做可以吗?很显然我们无法一口气吃掉这么大一个胖子,此时我们就会和产品经理砍需求。例如这样对产品经理的质疑:“这么多大公司做聊天,我们产品的特色是啥,核心需求是啥”,“我们面向的用户群体是哪些,初期该面向什么样的用户”,“我们的产品怎么分功能迭代发布给用户,保证产品想法快速落地”。我们之所以提出这些疑问,是因为我们需要识别架构的复杂度的点是什么?这也是我们为什么不直接复制微信架构的原因,微信的架构方案解决的是它所面临的业务复杂度的点,例如他们的架构可能是要解决全国的并发访问性能问题,而这个可能并不是我们在设计时想要解决我们的产品需求的问题。

所以我们总结一点:架构设计的主要目的是为了解决软件系统的复杂度带来的问题

2.3、不考虑开发成本

其实从上面的结论,我们不难判断这一点也是犯了类似的错误,过度的设计导致成本的增加,项目变得遥遥无期,上线后质量得不到保证。因此也没有解决产品想要解决的业务复杂度的需求。例如产品想要做一个匿名聊天软件,但你大成本的投入在了请求并发的性能方案,结果匿名功能投入的少,功能体验差,用户量少,以至于大成本投入的并发方案就更无用武之地了,这对一个项目而言不就是一个灾难吗?所以这是为什么正确的架构设计对一个产品而言是多么的重要。也应证了上面的结论:架构设计的主要目的是为了解决软件系统的复杂度带来的问题。

 

3、架构设计的重要性

架构即重要的决策,换言之:产品的想法最终的结果。恰如其分的架构设计是解决了产品目标需求中关键复杂度的问题,提升了该部分的用户体验,项目按时按质的完成,且能为产品后续的业务快速迭代做好了扩展。相反的糟糕的设计在当前互联网发展极快的时期,可能就错过了上线的最佳时机,失去了用户对产品品牌的信任,或者迅速被其它竞品抄袭,无法还手,这无疑不是一场灾难。所以你还觉得那些口口声声把架构重要性放在嘴边,但实际行动却不是如此的所谓的架构师真的理解了架构的重要性和架构的真实目的了吗?

所以至此我们结合上一篇文章关于软件技术发展的历史,和本篇文章对一些误区的分析,我们可以再次确认架构的目的:

架构设计的主要目的是为了解决软件系统的复杂度带来的问题。

这个结论虽然很简洁,但却是架构设计过程中需要时刻铭记在心的一条准则。

 

4、滴滴国际化项目案例

接下来我们用一个案例看下当架构师面对一个复杂的项目是如何正确的用架构的方法去拆解的:

 

4.1、架构设计

项目背景

目前大家用滴滴 App 在美国是可以打车的,对的,不用下载新的 App,现在的滴滴 App 在美国打开就会自动显示海外打车页面。

项目的复杂度分析:

滴滴出海面临的国际化在技术上有一定的特殊性,主要包括:

(1) 地图

地图作为滴滴客户端重要的支持及基础,而目前我们的友商都没有海外的路网数据,国际化我们需要接入新的国外地图提供商。

(2) 对接不同的运力

目前滴滴国际化是与海外投资的伙伴进行合作,比如美国打车跟 Lyft 合作。

(3) 漫游网络

目前国际化的主要用户场景还是国内用户出国打车,这时用户是用国内手机和运营商海外漫游接入网络。
以上的三个特殊性决定着我们需要在技术上的差异,下面的分享也围绕地图模块、漫游网络、多业务接入项目演进进行分享。

上面3个点就是滴滴国际化项目需要解决的产品复杂度问题。正确的分析出产品需求的复杂度,可以让方案设计更有针对性,更聚焦(集中有限的力量各个击破),技术选型更容易取舍。

 

地图SDK架构设计:

由于滴滴海外项目比较复杂,为了帮助大家更好的理解架构设计的目的,我们只针对地图这个复杂度的设计和技术演进来做分析。详细的滴滴海外项目介绍请参考《滴滴国际化项目 Android 端演进》

4.1.1、地图选型

技术背景:

  • 滴滴是个重度依赖地图的 App,而目前滴滴的友商及大部分国内地图提供商都没有海外的路网数据。
  • 滴滴前期针对的场景是国内用户海外打车,Google Map 依赖 Google Play Service,国内手机几乎都没有这个 Service

技术考察:

以上的背景要求我们去找一个合适的国外地图。所以需要通过技术考察和选择合适的地图:

  1. 功能是否满足:滴滴对地图强依赖,有些定制需求,如:很多 Marker 并且添加后需要修改、画圆并可以动态调整半径等等
  2. 接入效率成本:因为涉及到异地跨时区沟通,所以我们希望技术支持力度够大。
  3. 性能要求:性能包括地图启动时间、渲染速度、前端响应速度、后端响应速度。
  4. SDK包大小成本:在开始国际化前,当时滴滴的安装包就已经很大了,基本是国内主流 App 之首(当然现在滴滴 App 已经挺小了),所以我们希望新的地图够小。

地图对比结果:

地图选型分析:

这次我们调研了 Mapbox、Nutiteq、Here、Tomtom、Bing 共五款海外地图。其中
Bing 没有 Android 版;
Tomtom 有很古老的 Android 版,但功能过于简单,文档又几乎没有;
Here SDK 高达 40M,与他们沟通后,精简也只能到 25M,这个大小对我们是绝对接受不了的;
所以我们重点集成和测试的是 Mapbox 和 Nutiteq 这两家地图供应商。

Mapbox 和 Nutiteq 的功能和性能都满足我们需求,地图数据源也都是以 OSM(OpenStreetMap) 为主。
Mapbox 的 API 设计和国内地图类似,都是向 Google Map 靠拢,所以上手简单,并且整个 SDK 都是开源的,地图的样式也更美观些,而 Nutiteq 的地图底层设计比较独特,API 用法很不寻常,这也给我们接入带来了很大的麻烦。

Mapbox 有众多的 Web 用户,包括访问量都不低的 Foursquare、Pinterest 等,但 Android 端用户并不多;Nutiteq 的 Android 用户多些,但整体量也不是很大,不过我们并没有更好的选择,而且前期我们的量也不会很大,所以他们都在可接受范围内。

综合下来看的话,我们是更倾向于 Mapbox,不过 Mapbox 只能通过 GitHub Issues 和邮件反馈问题,反应很慢;Nutiteq 可以 Skype 沟通,效率很高。为了保险起见,Mapbox 和 Nutiteq 都做了全面接入和测试,最终证明这样是有用的。

跟多数 App 一样,为了使得包更小,我们的主工程配置了 abiFilter “armeabi”,仅打 armabi 的 so,而 Mapbox 的 armeabi so 无法跑在 armv7 机器上,前期集成测试我们通过修改 Gradle 脚本在编译时 copy so 的方式让测试通过,而 Mapbox 一直不愿意改,国内市场又不支持 Google 的 Apk Splits 机制,所以最终放弃 Mapbox 而选择 Nutiteq。
后话:经过过滴滴这次合作事件后,Mapbox 开始重视起国内市场,在国内成立了研发中心,沟通方便了很多,并且最新已经解决了上面 arm so 的问题。

 

4.1.2、地图切换

4.1.2.1、需求复杂度分析:

  • 用不了 Google Map 带来一个要求,我们选择的地图必须支持多国家,并且在设计时要支持以后不同地图任意切换。是的,即地图和 App 弱依赖。针对这个问题我们设计了地图隔离层。
  • 对接不同的运力,也就是滴滴在海外是和其它公司合作的。需要解决多个app接入的问题。

 

4.1.2.2、地图SDK架构设计

上图第二层 MapSDK 是地图的标准 API 层,App 只与此层打交道,标准层的 API 设计以 Google Map API 为标准。
第三层 Adapter 层是具体地图到标准 API 的适配实现层。每个地图都有个 Adapter,负责将地图 API 转换成标准 API。

将原来的 App 与三方地图直接依赖改为 App 依赖表示标准 API 的 MapSDK 层,由 MapSDK 通过具体的 Adapter 调用三方 SDK,这样地图切换只需要替换依赖的 Adapter 即可,其他地方无需改动。

4.1.2.3、新架构解决的问题:

(1) 解耦,切换成本低
这个上面已经介绍,再也不会因为换了地图牵一发而动全身。
(2) 学习成本低
业务开发人员只需要熟悉标准 MapSDK API 即可,不用了解其他地图的具体使用,时间成本降低。
(3) 通用
适用于所有 App,以后新增 App,可直接使用之前成型的 Adapter。

 

4.2、Android项目演进

4.2.1、原有模式

之前国际化业务的工程是很简单的方式,所有业务、组件、工具放在一起,根据具体包名划分:

这个在早期问题不大,并且开发起来快速方便,但随着更多业务接入,如我们前面说过的新的国家运力接入,问题就日益明显,包括:
(1) 组件之间耦合
虽然已经划分包名,但依然可以互相调用,组件间依赖关系不清,甚至有循环依赖。
(2) 添加新业务不便
(3) 开发问题
规模越来越大致提交冲突可能性变大。

4.2.1、 SDK 工程提取

将原工程整体拆分为业务工程和 SDK 工程,单业务工程直接依赖 SDK,可独立开发、独立运行、独立打包。如下:

这样在接入新的业务后,总体项目结构如下图:

每个业务作为单独工程,共用组件、工具、业务统一到 SDK 层中。
集成工程负责集成 Lyft、Ola、GrabTaxi 项目,所有业务项目提供 AAR,由集成工程整体打包对外发布。

4.2.2、 SDK 工程组件化拆分

为了解决组件之间耦合,防止后续问题加剧,同时方便协同开发和更好的复用,将 SDK 工程组件化拆分如下:

SDK 整体拆分为 Business Library 和 Util Library 两大部分,主要依据是是否可以独立于我们业务,他们间不允许反向依赖。每个部分包含若干组件,每个组件都以 Module 形式存在。

Business Library 为通用业务层,包含通用业务组件,如平滑移动、上车点、定位、地理信息、打点、网络封装。 其中 CommonBusiness 存放暂时通用、但尚不足以作为一个单独组件的公共业务,以后可能独立出来,注意包名规范方便未来独立。

Util Library 为工具库,大致分为 View 和 Util,DidiSDK 为滴滴 App 整体通用组件包,包含通用的图片缓存、网络请求、基础登陆组件等等。

4.2.3、SDK 组件化拆分后依赖关系图

、通过上图我们可以发现即便只是 Business Library 层,组件也根据依赖关系划分为明显的上下层。

4.2.4、 SDK 组件化划分事项

(1) 单一及开闭原则
每个模块只代表一个功能模块或一个公共业务,对于个性化或定制功能以接口形式对外开放。
PS:目前 CommonBusiness 模块暂时作为国际化 SDK 整体集成打包的模块,即国际化 SDK 项目中的 sdk Module,后续当其中某个公共业务足够成为一个模块时可继续拆分出来。

(2) 拆分粒度
项目的演进是不断进行的,没必要将每个细小组件都拆分出来,这样不仅增加了项目的复杂度,同时也会影响编译时间。
先根据实际需要拆分必要的组件,太小暂不足以独立的组件可以在以后不断进行的重构中根据需要拆分。如上面的 CommonBusiness 模块,当然需要保持一定的规范方便以后拆分。

(3) 依赖关系
通过依赖图整理依赖关系,防止重复依赖,同时看出沉淀关系。
1. Util Library 不能反向依赖 Business Library;
2. Business Library 除了基础部分,如 Net、Geo、EventTrack 外,其他部分尽量不要相互依赖;
3. Business Library 中 Net、Geo、EventTrack 不允许反向依赖其他模块。

(4) 开发规范
为了保证扩展性及方便以后继续拆分:
1. 所有业务包名以 com.didi.{xx}.sdk.{businessName} 开头;
2. CommonUtil 模块中所有工具包名以 com.didi.{xx}.sdk.util.{utilName} 开头;
3. CommonView 模块中所有 View 包名以 com.didi.{xx}.sdk.view.{viewName} 开头;

(5) 组件间通信
放弃原来造成耦合严重的 EventBus,改用原生的通信方式,包括原生 (startActivityForResult) 、内部广播、回调等。

4.2.5、SDK 组件化项目整体设计图

其中虚线部分为 SDK 层。

4.2.6、 组件化拆分后的好处

(1) 组件间解耦
(2) 业务并行开发、测试
(3) 组件单独测试

 

5、小结

本文阐述了架构设计的目的——架构设计的主要目的是为了解决软件系统的复杂度带来的问题。以及用了滴滴app出海项目的一个例子让我们了解到滴滴架构师是如何分析软件系统的复杂度,以及如何通过架构和技术手段去解决这些复杂度带来的问题。

同时我们也需要知道任何一个复杂,炫酷的架构图都有其背后与之对应复杂度的业务。所以当我们在做一个业务时,不能盲目的复制其它竞品的架构设计,这样可能会导致复制过来的架构水土不服,例如复制过来后不适应业务的需要,会大改,造成不必要的麻烦。

架构设计的复杂度和业务的复杂度是等比的,往往业务初期产品为了快速让产品上线,业务复杂度会比较简单,此时的架构设计的复杂度也应该比较简单,但架构师需要考虑到架构的可扩展性,以便适应产品复杂度的增加带来的架构复杂度提升过程中能平滑过渡,可以快速应对产品的变化。

赞(0) 打赏
未经允许不得转载:花花鞋 » 架构学习3——架构的目的
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

国内精品Android技术社区

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏