豌豆荚范怀宇:我们在豌豆荚2.0重构时遇到的坑

豌豆荚范怀宇:我们在豌豆荚2.0重构时遇到的坑

产品 豌豆荚 MDCC
摘要:2012年10月19-20日,中国移动开发者大会2012在北京国家会议中心举办,豌豆荚 2.0 重构项目技术负责人范怀宇在“平台与技术”分论坛上做了题为《豌豆荚2.0重构经验谈》的主题演讲,演讲中,范怀宇向与会者分享了开发豌豆荚2.0的思路和解决方案。

重构还是重写,这是个问题

在明确了问题之后,接下来的选择是:重构,还是重写?

所谓重构就是沿用1.0的架构方向,继续选择.Net平台,把我们认为耦合严重的代码重新整理,使它的结构更加清晰,能够支持未来的需求开发,这样的重构模式好处是开发成本低,但它的弊端是无法解决我们在.Net平台上碰到的性能之类的顽疾。

另外一条路就是重新写,我们从零开始,再做一个豌豆荚,我们使用全新的架构降低耦合,改用C++这样的Native语言,来够解决豌豆荚1.0中的启动速度慢等性能的问题。但可以想象,重写的成本非常高,在豌豆荚1.0版本的代码积累完全不能复用。并且,在2.0版本重写的同时,1.0版本还需要不断维护,出新功能,这对于当时一个30人团队而言风险还是很大。

但是综合考虑了这两种策略的优缺,我们相信,在未来的一两年甚至更长时间,我们还是会不断地做很多新功能,做更多的产品尝试,需要不断提升性能等基础体验,因此,我们需要的是一个更稳定更高效的架构,于是,我们选择了重写。

走适合自己的路

选择重写,我们评估过不同的解决方案。比如说,使用传统的Windows架构,所谓传统Windows架构就是类似于MVC的架构模型,界面用WTL来搭,在Controller中把业务逻辑塞上去。这样的架构在过去十几年的Windows开发中普遍被采用,相对来说,开发效率会比较低,面对我们想要的新的产品的效果以及需求来说,做起来会很麻烦。比如说我们想加入一些动画效果,一些很酷的切换效果,都会需要很长时间来开发。于是就想了另外一种比较流行的解决方案,就是DirectUI,在这样的解决方案里面,我们会用XML的配置文件来描述界面,使用C++等高效本地语言来实现逻辑,用Lua、Python这样的脚本来做粘合。这样的解决方案能够在保持高性能的同时,提供更漂亮的界面效果。

基于DirectUI各公司有不同的实现,也有一些放出来给大家用的解决方案,比如说迅雷的Bolt,但是这些方案在我们看来都有一个弊端,就是比较封闭,没有一个在整个业界足够有影响力的通用解决方案。一个相对封闭的解决方案意味着,无论我们招什么样的人,都会面临着不小的学习成本,因为需要他了解这个平台,然后再了解豌豆荚的开发。对于一个小公司来讲,这样的学习成本确实十分高。

用“Web的思想”做PC客户端

此外,我们还可以继续沿用1.0的思想,还是基于Web的方式来开发客户端应用。所谓基于Web的方式,就是在整个应用内部,绝大部分的UI设计和通信模式都和Web应用类似,用HTML以及CSS、JS来绘制界面,通过Ajax等调用来获取数据。

为了提高整个Web体验,我们选择内嵌一个Webkit内核,来保证前端开发的简易性。同时,鉴于1.0的经验,我们觉得一个灵活规范的通信模式,在这样的架构中是非常重要的。使用这样的架构,保证了开放性,在开发人员上也可以有更多的选择,并且可以很好的满足我们对产品的需求。基于这些考虑,我们最终选择的方案是继续使用基于Web的技术来开发Windows应用。

但是这个解决方案我们并没有任何可以参考的成功案例,对于豌豆荚来说有很多技术问题都是未知的。为了能够把这样的重构做得顺利,我们先制定了很完整的产品需求文档,它取自于一年多来的经验。我们把其中一些证明好的功能做了重新的设计,把一些用户觉得不好的功能给拿掉了,并且通过我们的判断,增加了一些提升基础体验的新功能。比如说多设备管理,设备数据离线缓存等等。在产品设计基础上,我们花了比较长的时间,重新做了架构设计,其中特别针对1.0中碰到的一些技术障碍,给出了更优的解决方案。

还有一个重要的点就是我们做了很多的demo来尝试,尝试整个架构方案,看看我们觉得有瓶颈的地方是否能解决,通过这些demo,使我们坚信我们可以沿着这个技术方向来做应用,把2.0版本搭建起来。

全新的豌豆荚2.0的架构其实看上去和Web应用比较相似。前端页面主要基于Webkit内核,用Html、CSS、JS的技术绘制界面,满足标准的Web规范,甚至可以跑在Chrome上来测试。其他需要和Windows的一些原生效果比较相似的界面部分,我们从Chromium中抽取了一套UI框架来构建。在后端的逻辑主要分成两个部分,一个是平台层,提供基础的功能服务,比如说与手机通信,和服务端通信,和操作系统通信等等。

豌豆荚范怀宇:我们在豌豆荚2.0重构时遇到的坑_第1张图片

图:豌豆荚2.0技术架构图

除此之外,我们把业务逻辑封装成不同的服务,每一个服务最重要一点是独立,彼此之间完全没有耦合。服务和服务之间,后台和前端之间,全部通过通信层来进行交互。在通信层上流转的数据,我们称之为communication数据,这些数据都是用ProtocalBuffer来描述。

通信层是整个架构比较核心的部分。它的协议定义是来自于一个开源的项目——Onering,这个项目的初衷也是受了豌豆荚1.0的启发,觉得1.0采取的基于Web开发客户端应用的架构很符合未来的发展,在这个基础上,他们做了一些协议的定义,把整个通信方式模拟得更像一个Http请求,把通信端分成三个部分,一个是前端,一个是后端服务,另一个是操作系统,定义了这三个端,六个方向的通信实现的策略。我们主要参考了其中的协议定义,在实现方面,Onering项目原来实现会比较简单和粗放。在豌豆荚的实现中,我们使用了定义的Webkit和C++语言作为前后端的实现基础,用Protocal Buffer来描述通信协议的数据。

豌豆荚范怀宇:我们在豌豆荚2.0重构时遇到的坑_第2张图片

图:OneRingAjax

在前端开发中两个最主要的通信方式,一个是主动调用,它的使用方式非常像Ajax调用。举个例子,如果前端想请求123这台设备上的联系人数据,并且以json这样的格式来获取,那么他就可以发起这样一个url描述的请求,请求到AppManager这一层会做一个统一的调度,AppManager是通信层的主要实现模块,里面有应用中所有服务的信息,它会根据url找到对应的服务,用线程安全的异步调用将请求发至对应服务进行处理,所以流程与普通的Ajax请求无异。

不但前后端调用如此,服务和服务之间的通信也会完全采用同样的模式进行通信。比如说联系人服务想获取短信信息,它不能直接访问短信模块的接口,而是通过同样的方式发起一个异步请求,来获取短信的数据,以此来保证服务之间的低耦合,便于业务逻辑的扩展。

除了主动调用的模式,还有一种被动回调的模式。比如说如果联系人模块中数据更新了,从手机上获取的新的联系人数据,怎么通知前端进行刷新?我们采取的方式叫做事件/订阅的模式,前端订阅这么一个事件,当后端触发该事件的时候,也通过一种异步的回调来通知订阅者,并保证该调用是线程安全的。通过这两种通信方式,基本上满足了前后端和服务之间的所有通信,在豌豆荚2.0的实现中,被大量使用。

整个架构上中还有一个很重要的就是服务。我们希望服务之间是没有耦合的,所以在实现时我们只为每个服务定义了非常简单的API,它只有一个接口接收请求,一个接口释放数据。所有的服务都不会被其他服务来直接调用。在实现上,服务可以封装成DLL,用任意语言来实现。在豌豆荚2.0的实现中,我们考虑到了整个应用的体积问题,我们选择使用了C++的静态库方式来实现。在服务内部的设计,我们会让它更贴近我们的产品需求,我们按照设备来划分线程逻辑的,使得设备之间是完全独立的。比如你同时使用两台设备,每台设备上的缓存数据,与手机的通信,都彼此都是独立互不影响,这样可以保证同时使用两个手机的时候,都可以有很流畅的交互。

在豌豆荚2.0当中,其实我们使用了大量的开源软件。我记得在第一次提交代码时,我提交了180万行代码,其中包含了很多开源软件的实现。其中,最核心的一部分是从Chrome开源项目中抽取的基础架构部分。Chrome已经成为了业界标杆,不仅是所有的浏览器,很多现在Windows的程序也会同样采取Chrome的架构以及Chrome的一部分的代码实现。Chrome给豌豆荚带来的最重要的东西是编程模型,每个线程都有消息循环,跨线程的操作都是异步调用。因此,在实现业务时不需要关注数据访问时的并发性问题。还一个很重要的部分,Chrome对操作系统的原生API进行封装,提供了更为建议可靠的API,使得开发时可以尽量少地和操作系统API打交道。此外,它还对很多常用算法进行了封装,比如Md5,比如Sha,等等。Chrome也可以对STL做了很好的加强,对更丰富数据结构和API支持。

其实有一个基于Chrome的开源的项目叫Chromium Embeded Framework。这个项目抽取了Chromium中核心的部分,整个架构和豌豆荚2.0采用的颇为相似,现在也是被很多公司采用来开发软件。整个豌豆荚2.0的架构总结一下,其实很重要的一点就是,我们使用了一个比较新的技术的尝试。


你可能感兴趣的:(豌豆荚范怀宇:我们在豌豆荚2.0重构时遇到的坑)