写点什么

手机天猫解耦之路

2016 年 12 月 28 日

先要说一句:惭愧,惭愧。这本来是今年夏天在北京 GMTC 上的分享,其实早就应该整理成文。哎,拖延癌晚期的症状始终没有缓解,又恰好找到了一个要做双 11 的借口,至此。

在 GMTC 官网可以下载这次分享的资料: http://ppt.geekbang.org/slide/show/194

现场视频已上线:《手机天猫解耦之路》现场

本文标题是解耦,聊解耦可以有很多方法,本文以架构进化为线索给各位分享手机天猫的解耦之路。我想,在手机天猫的成长过程中,一些形而上的思考和沉淀固然是对大家有参考价值的,而工具和方案则借鉴价值更大。所以本文会较少篇幅放在讲过程和原因,比较多篇幅放在讲工具和方案。

什么在推动进化

作为技术团队,我们升级技术架构有各种原因,而什么什么因素是最关键的,什么可以成为进化理由?

  • 业务升级

    最重要的因素一定是业务升级。一个不能产生业务价值的技术是不值得关注的,更不值得去实施。

    好技术永远要比业务先走一步,在前边不远的地方等着业务追上来——技术驱动业务。所以业务不断升级,就要求我们的技术架构要跑的更快。

  • 团队规模 & 合作模式

    另一个推动架构进化的重要因素就是团队规模。技术架构最重要的作用之一是保障生产效率和生产质量。那么人的因素就非常关键。人多了,合作复杂了,技术架构就必须升级,去保障这么多人在一起工作的时候,效率高,不出问题。

  • 代码规模 & 工程规模

    代码和工程的规模是一个自然发展的结果,业务复杂了,团队大了,人多了,代码规模和工程规模必然上升。一个数十万 DAU 的 App 和一个千万 DAU 的 App,规模上的差距虽不是必然,却也是极大概率了。

    一个人,几十个文件,完成一个简单功能的产品,和十几个人,数千个文件,功能复杂的产品,进一步到十几个团队,数百个模块,一个平台及产品的技术架构必然千差万别。

  • 新技术

    新技术,就是工程师自己的事了。业界总会有新的技术出来,这个技术可能是别人家工程师,为了适应他们的业务发展和架构演化而研发出来的。但是技术没有边界,新技术好,能给我们带来价值,提升我们的效率,那我们就拿来用。

    举个例子:Facebook 的 RN 出来,我们都觉得不错,应该也有很多公司确实大规模的使用了。大规模的使用 RN 做开发,也就会对你原本的架构提出升级的要求。

当然更重要的是如何去平衡快速升级技术架构的好奇心和恰好满足业务与团队要求这两件事。过度追求技术架构革新,过度追求新技术,不但不能给业务和团队带来推动作用,反而会造成灾难。所以,作为一个优秀的技术团队永远要权衡做或不做,多做或少做。

架构怎么进化

架构进化体现在哪些方面,作为一个技术团队我们要如何把架构进化落地?这个问题因项目而异,因团队而异,因方向而异。本文只介绍手机天猫在发展过程中,与解耦相关的进化历程。

  • 升级开发模式

    开发模式的概念有点大,本文就只讨论和解耦这件事相关的:团队合作方式和工程组织形式。下文单独一节聊这个事,此处不赘述。

  • 各维度解耦

    工程大了以后,要分拆,不管是组件化还是插件化,还是什么,解耦是第一步,而且是各个维度的解耦。

  • 完善工具集

    模式演进的过程中,解耦的过程中,就会衍生出很多的工具。在进化过程里我们也会去思考,哪些工作是需要工具化的,主动去开发工具。一个完善的工具集,会极大提升团队的生产力,可以说是最有价值的部分。

开发模式升级

手机天猫团队从一个三端不到十个人的小团队,成长到现在一个接近两百人的大团队,后文详细描述开发模式经历了怎么样的变更?

  • 一个工程

    三年前,手机天猫团队刚刚组建,十个左右工程师,开发第一版只具备基础功能的天猫 App。整个团队就这么几号人,包括 iOS,Android 和 Server 三端,一个平台上也就三四个人;App 的功能也非常简单,能完成基本的导购和交易流程。

    天猫 App 就使用了最简单的架构,独立工程,MVC 架构。而且我们判断在这种情况下这样的架构是完全够用的,事实如此。

  • 模块化

    随着无线业务的发展,手机天猫的团队开始爆炸式的扩张。很快一个团队变两个,两个变四个。随着团队增加,出现团队分工,工程也越来越大,我们开始发现原始的架构已经开始不够用,拆分模块势在必行。

    在这个阶段,手机天猫的模块拆分也做得非常简陋。先按功能把工程做横向分层,在业务层再做纵向梳理。把不同的模块代码简单的放在一个文件夹里,而工程的组织形式并没有发生变化。

    如此拆分,我们做到代码独立,跨团队基本不会在同一个模块代码上产生冲突。

  • 插件化

    进一步发展,业务越来越复杂,团队工作越发细分,人也越来越多,代码量越来越大。简单的使用文件夹来组织模块的方式显得力不从心。多业务跨团队,不同的开发节奏,复杂的依赖关系,导致我们会花掉大量的时间解决编译不过的问题。等待其他模块集成这件事居然成了我们开发效率最大的瓶颈。

    如何解决这个问题,我们的方案是插件化。那么插件和模块有什么区别?我认为二者最大的区别在于独立性。插件是可以独立开发,独立发布,独立运行的,而模块则必须依赖主工程的环境。具备独立性的插件可以很好的隔离跨团队之间的依赖,彼此独立开发,按照各自的节奏发布版本。

    基于这样的思考,我们引入依赖管理设施(iOS 引入了 Cocoa Pods,Android 使用 Maven);把此前的模块进一步剥离成独立工程,单独做版本管理;每个独立的插件对发布的版本号负责,不论是其他插件还是主工程都依赖插件发布的稳定版本。

    然而,但是,But,插件化这件事并没有我们想象的那么美好。代码出来了,但是不能独立编译,依赖管理设施有了,但是管不好。由于我们此前从未梳理过依赖关系,所以不管是模块还是插件,只是一种代码管理和发布流程的工作法,解决不了独立开发和独立运行的问题。在这个阶段,我们选择了容忍这个问题,因为独立开发和独立运行这两件事对我们来说似乎并不是那么的有价值,而无法实现这两件事也并不成为我们的瓶颈。所以大家还是在一个工程里,只是代码提交到不同的仓库,然后通过依赖管理设施,通过版本号拼装成主工程,源代码最终运行还是揉在一起。

  • 独立发布

    无法独立发布会带来什么问题?非常明显,!插件化一段时间后,我们发现慢的问题严重影响着我们的效率。在这个阶段,我们已经有超过十个团队,iOS 工程的源码文件超过一万个。由于主工程是通过各插件的源码组合起来的,每一次重新索引和编译,都要消耗超过半个小时的时间。

    要解决这个问题,就是要把插件化进行到底,实现插件的另外两个独立——独立开发和独立运行。最重要的工作就是我们今天的主题解耦,梳理各个插件之间的依赖关系。让每一个独立插件尽可能少的依赖其他插件,在最小范围内正常编译执行。每次发布不再是一个稳定版本号,而是一个稳定的二进制包。

    如此依赖,我们把超过半小时的编译过程拆分到数十个模块中,而主工程依赖数十个二进制包,编译也就快了。

整个模式升级基本上经历了这样几个阶段:

  • 代码独立,先从形式上解耦
  • 独立代码工程化,为独立运行打下基础
  • 梳理依赖关系,独立工程可编译
  • 放弃源码依赖,提速集成编译

一路走来,一步一个脚印,最终实现完整的解耦。在这个过程中我们沉淀了不少的方法论和最佳实践,我想有两个工具是值得介绍的,下文详述。

解耦工具箱

工欲善其事,必先利其器。这句话每个人都在说,却不是每个人都能做到。一个具有工具文化的团队会在质量,效率各个方面都会有很大优势。

一个工程,从原始状态迅速膨胀到天猫现在的体量的,依赖关系之复杂,超乎想象。

在这个膨胀过程里,我把耦合分成三类:

  1. 界面耦合,就是用户操作流程里,从首页 - 到搜索 - 到详情 - 再进店,这些界面的跳转是硬编码的
  2. 依赖耦合,顾名思义,两个模块之间的有依赖,就是耦合
  3. 工程耦合,每个模块有自己的生命周期和运行时,每个模块在生产环境里又需要依赖主工程的运行时

Beehive 是一个运行时框架,主要解决依赖耦合和工程耦合。

说到耦合,体量如手机天猫这样的一个 App,各种依赖关系必然非常复杂,模块与模块的耦合也必然千丝万缕。我们要做的并不是把这些依赖和耦合一一处理掉,而是进行梳理,把不合理的找出来,解决掉,让整个工程处在一个健康合理的依赖和耦合范围内。有问题的依赖基本有这样几种:

  1. 模块循环依赖
  2. 层间反向依赖
  3. 非强功能依赖

下图是一张依赖的示意图。

几条虚线的依赖关系是我认为有问题的依赖,而抽象出有问题的几个模块

引入 Beehive 后,依赖关系会把几条红线全部引向 Beehive 模块,而 Beehive 模块则是独立于各层之外的。

Beehive 的原理是,每一个对外提供服务的模块,需要注册一个抽象接口到 Beehive 提供的 Interfaces(接口池)。注意,在这个池子里只有抽象接口。

开发阶段,调用方依赖接口池中响应的接口,并以接口为参数,通过 Beehive 提供的工厂方法获取一个服务实例,这个实例可以正常进行服务。

运行时阶段,Beehive 工厂方法根据服务的注册配置,构造服务实例。若:当前的运行环境没有依赖提供服务的模块,则返回空;若:当前运行环境依赖关系完整,则开始构造服务,并返回。

通过这样的方案,就可以实现模块间解耦。

统跳协议 & Rewrite 引擎

统调协议是一个基于 URL 的跳转方案,配合 Rewrite 引擎实现全 App 调用解耦。此前苹果核一篇文章详细介绍,这里我就不详述细节。

Beehive 和统跳 &Rewrite 的区别

Beehive 和统跳协议的目的都是解耦,然后二者所关注的重心不同。统跳主要为界面解耦服务,业务要求界面链路的强动态性;Beehive 则为模块解耦,解决模块强依赖带来的开发阶段痛苦。

以上,就是我们在过去的几年里,整个手机天猫所经历的解耦过程。在这个过程里,我们有过很多思考,也踩了很多坑,当然也沉淀了很多好用的工具。希望接下来能有更多机会跟各位分享,也欢迎各位跟我们交流,互相学习。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 12 月 28 日 16:388408

评论

发布
暂无评论
发现更多内容

SpringBoot中的响应式web应用

程序那些事

spring WebFlux 程序那些事 响应式系统 spring 5

极客大学 - 架构师训练营 第九周作业

9527

详解快速开发平台与工作流通用组件的设计规范

Philips

敏捷开发 快速开发 企业应用

不可思议,竟然还有人不会查看GC垃圾回收日志?

田维常

垃圾回收 GC

架构师训练营第 1 期第 8 周作业

du tiezheng

极客大学架构师训练营

京东11.11完美收官!京东智联云以技术服务助力实体经济

京东科技开发者

云计算 大数据 云安全

线程池 ThreadPoolExecutor 原理及源码笔记

程序员小航

Java 源码 jdk 线程池 并发

数据库建表、SQL、索引规范

Bruce Duan

MySQL sql 建表 规范

极客大学 - 架构师训练营 第九周

9527

互联网应用架构目标及技术方案

将减少阻力的香蕉法则,运用在软件开发上会产生什么效果?

Philips

敏捷开发 快速开发 企业应用

熔断原理与实现Golang版

Kevin Wan

go microservice

charles的使用方法

Yolanda_trying

重大活动网络攻击面前,京东智联云的攻防之道

京东科技开发者

云计算 网络安全 云安全

第八周作业

springboot+java+redis 简单实用的搜索栏热搜,个人历史记录,文字过滤

灰尘子

直播预告 | 云原生在CloudQuery中的应用与实践

CloudQuery社区

数据库 sql 容器 云原生 工具软件

一个数据中台如何算成功了?

薄荷点点

数据中台

BATJTMD,大厂招聘,都招什么样Java程序员?

小傅哥

Java 互联网 面试 小傅哥 简历

关于mysqldump,这个参数你可能还不知道

Simon

MySQL timestamp

面对大促DevOps怎么做?这里有一份京东11.11 DevOps备战指南

京东科技开发者

云计算 DevOps 运维自动化

分布式集群如何实现高效的数据分布

vivo互联网技术

分布式 DHT hash 数据存储

【架构师训练营 1 期】第八周学习总结

诺乐

浅谈软件研发管理体系建设

大黄蜂

HTTP 前世今生

大导演

HTTP 前端进阶训练营

虽然世界给我们变化,但让我们的人生更向幸福靠近一点点,而入门票就是自学这回事

叶小鍵

《Java程序员修炼之道》.pdf

田维常

面试重灾区——Synchronized深度解析

执墨

并发编程 synchronized 内存布局 CAS 锁升级

数字信封加密

莫问

找出两个链表中合并的元素

第八周总结

打造 VUCA 时代的 10 倍速 IT 团队

打造 VUCA 时代的 10 倍速 IT 团队

手机天猫解耦之路-InfoQ