淘宝特价版为了解决app自身的研发效率,用户体验问题,引入Flutter框架,并结合FaaS进行云端一体化融合。一个开发者可以在框架内顺畅的完成前端+后端的开发,相对于传统Native开发交付流程,节省一半以上的开发成本。本次分享将由淘宝特价版开发团队 iOS 高级开发工程师李彬为大家详细介绍淘宝特价版开发体系在探索过程中遇到的问题,以及基于Flutter和FaaS的页面构建方案。
演讲嘉宾简介:李彬,花名潇侠,特价版开发团队 iOS 高级开发工师。曾做过Tangram开源动态化框架,现负责特价版前后端一体化的开发模式升级,落地Flutter。
以下内容根据演讲视频以及PPT整理而成。
观看回放http://mudu.tv/watch/5708706
本次分享主要围绕以下三个方面:
一、淘宝特价版遇到的问题和解决构想
二、基于Flutter和FaaS的页面构建方案
三、前后端一体化模式的进一步探索
一、淘宝特价版遇到的问题和解决构想
特价版业务开发遇到的问题
首当其冲的是研发效率,首先,传统的开发方式分为安卓、iOS、服务端,至少投入三人,人员投入多,交付效率低,当iOS和安卓人员分配不对等时,将引发资源分配的问题。其次调试慢,Native的开发使用xcode或者Android Studio调试时,debug一次需30s甚至更长,阻碍开发进程。然后是双端逻辑不一致,安卓和iOS由两个开发者负责,代码逻辑可能会有所不同,进入测试阶段会出现两端不一致的情况,导致重新开发。第二个是双端体验,安卓和iOS均投入人员优化,才能达到双端体验。需要解决帧率低、加载时间长、内存占用大的问题。最后是规模化,存在技术方案基础不统一、没有持久支持的社区、无法推广到全链路业务的问题,一旦更改其他方案,则之前所有的沉淀将推倒重来。
传统的交付流程,客户端和服务端约定mock数据,客户端根据mock数据制作View,之后需要多人协作,安卓和iOS共同开发,完成Native逻辑,前后端联调。服务端在约定mock数据后,生成服务端Model,然后请求其他数据源,然后完成JSON数据拼装。此时两端联调,结束后,客户端交付测试,服务端进入运维阶段(评估一个服务请求多少QPS、RT如何优化、需要多少台机器)。
传统的交付流程拉长需求时间线,例如开发阶段,iOS需要五天,安卓需要五天,服务端需要三天,后面两天在等待,导致服务端时间线凌乱。联调阶段,服务端和客户端约定字段,当字段逻辑不一致时,两端需要互相沟通,确认字段逻辑,这部分会占据开发流程30%的时间。测试阶段,发现两端逻辑不一致时,又需要回溯开发阶段确认解决。整个流程存在Native调试慢,逻辑必须双端开发、前后端沟通成本高、双端逻辑不一致的问题,拉长了交付时间。
针对以上问题,改善交付流程,实现前后端一体化的MVVM开发体系,减少沟通成本和开发人力的投入,一个人可以贯穿整个开发流程。首先一个开发者,在服务端创建一个Model(请求数据源并汇总),接下来是视图,应用跨端技术,实现一个开发者开发双端,然后是客户端逻辑和视图绑定,即View Model,将Model的数据处理后绑定到View上,实现解析流程,最后交付测试。整体过程有一个开发者负责,不需要安卓、iOS、服务端特定开发人员,降低沟通成本,避免双端不一致,减少一半以上的人力投入,从过去的24人日到现在的10人日。
为构建以上交付流程,制定了前后端一体化方案。抽象来看,传统的MVVM,客户端和服务端割裂,客户端是一个单独的MVVM的模型,即View到ViewModel到Model。服务端是MVVM或者其他框架,只需要输出JSON格式数据,和客户端的流程完全割裂。本方案期待将MVVM贯穿前后端开发的流程。拆分客户端的Model,传统MVVM中客户端的Model主要请求服务端或者ABC接口,并将三个接口合并为model类,传递给ViewModel。数据的组装逻辑在服务端Model完成,数据的同步逻辑在客户端的ViewModel完成。而新的体系中,前后端整体是一个MVVM模型。为了完成一体融合,需要框架封装,服务端包括:数据同步、页面模型、数据源接入和容灾打底,客户端包括订阅流、布局能力、动态组件和数据绑定。于开发者视角,首先看到的是组建方案,即用怎样的组件完成编码(动态组件/硬编码组件),其次是VIewModel,仅需要知道订阅的数据是什么,不需要关心数据从何而来(请求哪个接口,从哪里获取数据),最后是云端的数据Model,服务器完成数据逻辑,汇总输出数据Model。云和端通过框架层同步数据,数据协议对开发者完全屏蔽。
二、基于Flutter和FaaS的页面构建方案
为了构建框架,客户端和服务端均需要基础支持。客户端要求双端一致、调试快、性能高、社区支持,选择Flutter,云端需要发布快、易上手、成本低和屏蔽运维细节,最终选择FaaS服务(Function as a service,简单来说是一个服务端容器)。FaaS服务弥补Flutter无法动态的特性;延申开发领域,客户端开发人员可以开发服务端需求;Function对应MVVM中的M,位于前后端一体化Model的位置。FaaS容器支持多种语言,包括Java、Python、Go,最终因受众广(开发人员一般均接触过Java)、生态广(基本框架丰富)、工具丰富(IDE工具,降低开发门槛)的特性,选择了Java语言。
FaaS相比于传统服务端开发存在这几个优势:首先服务端无需管理服务器,传统交付流程中,存在服务端运维的环节,需考虑QPS、机器等,而使用FaaS后,减少开发人员需考虑的事情。其次是资源弹性伸缩、事件触发执行和不执行不付费。在淘宝特价版的开发体系中,Function可以作为MVVM的M,这是选择FaaS而不选择PaaS的最重要理由。
FaaS功能强大,服务端所需要的功能在FaaS里面有所体现,包括函数计算、对象存储、API网关、表格存储、日志服务和批量计算。函数计算的客户只需要编写代码并设置运行的条件,即可以弹性、安全的运行。
FaaS的示例,一个请求到达API网关,FaaS容器里面包含Function代码,实现数据输出。Function即一个函数,包含输入参数和输出对象,不论是对于iOS还是安卓开发者,简单易上手。
以特价版首页介绍基于Flutter和FaaS的方案,第一阶段基于一个集中数据接口的方案,具有普适性,第二阶段持续演进的一体化方案。
基于基于一个集中数据接口的方案,前端使用Flutter容器,后端使用FaaS服务,页面配置和业务数据统一在一个数据输出接口,以JSON作为数据协议。Flutter容器分为四层,最底层是基础层,用于和基础技术对接,包括请求SDK、启动流程、事件通信、图片库。往上是组件层,包含Dart组件和动态组件。再往上是布局层,包含布局引擎和布局容器。最上面一层是页面层,有页面引擎、数据处理、页面基础能力(例如埋点和点击跳转等)。基础层、组件层、布局层和页面层都是可以被其他页面复用,最终的业务定制层即页面业务逻辑,四层复用可以有效提高开发效率。
FaaS容器底层是基础接入层,有算法、投放和外部数据。之后是模型层,和客户端对应,例如客户端的布局层、组件层、页面层,在服务端模型层都有对应,即页面模型、布局模型、组件模型接口。模型层之上是输出引擎层、请求接入层,最上面是业务定制的部分。服务端的一个特点是,业务方不需要操作底层object或JSON,而是操作模型层的对象,可以屏蔽数据协议的影响。
客户端页面分层架构,由下至上为组件层、布局引擎层、页面引擎层和业务层。在实际的调用流程中,业务层逻辑包含滚动视图以外的部分,例如浮层、性能监控和页面埋点,滚动视图以内的部分由页面引擎负责。页面引擎的入参为id,通过id做请求,此时对接FaaS的接口、缓存、请求上下文序列化,在FaaS的Function入参获取请求上下文的内容。页面引擎同时负责基础的业务能力,即刷新、翻页、点击跳转和容器埋点。请求数据后获取JSON格式的数据,传输到布局引擎。布局引擎解析视图,生成Page对象、Tab对象、Section对象。生成视图后对接到组件,组件包含Dart组件和XML动态组件。
淘宝特别版的页面结构可拆解为如上图所示。根节点content,体现在数据中,之后页面被拆机为四个部分:顶栏(topbar)、tab之上(headerSection,可以理解为后续在tab上插入图片或者组件)、tab本身(tab)和tab之下(feeds,feeds中可能包含多个feeds,tab中有个类目,每个类目下存在多个容器,feeds首先进入tabContainer,之后进入container,存在单列和瀑布流的形式)。页面到容器到组件,业务方拿到的是page,page即Flutter中的view。page包含widget、TabBar、TabBarView子节点,接下来是CustomScrollView、Sliver和widget。Flutter中连接List或者Grid极为方便,例如混合一行一列和一行两列,用CustomScrollView可以很好解决。
开发者视角,传入id给页面引擎,获得widget。引擎层面有页面引擎、布局引擎、组件引擎,页面引擎执行请求,获取数据给布局引擎,布局引擎生成布局,寻找组件给组件引擎,组件引擎生成组件。业务方获取到page对象,可直接贴在页面。
服务端方案使用FaaS,有以下特性:函数计算,使用serverless平台,无需管理服务器资源,降低操作成本;选用Java语言写FaaS,降低上手门槛,可以使用Java的生态;Function是可以作为MVVM的M,可以更好实现前后端一体化;让客户端来写服务端,需要在服务端构建一套和客户端结合的框架。
服务端分层架构由下至上为基础对接层、模型层、引擎调用层和业务层。和客户端类似,滚动视图之内的部分由业务层逻辑负责,页面引擎接收客户端发送的id,通过页面Function调用链读取配置,做请求上下文序列化,之后Function获取具体组件的Model、容器类Model、Model反序列化工具。底层是基础对接,包括日志服务、监控、容灾打底、发布流和存储等。
客户端所见即所得,即按照看到的页面(视图)做拆分,而服务端按照数据源做拆分。数源可能是一对一,多对一或者一对多的关系,需要具体情况具体分析。Function对接的数据源数量不固定,接下来输出的Function模型对象即widget(组件),页面Function组织组件,将其放入container中,最后page对象将其序列化输出到客户端。
首先页面Function接收id,进入页面引擎后,读取id对应配置即需要读取哪些子Function,子Function输出模型对象(组件对象),模型对象会被汇总到页面Function中,然后可以获取子Function的返回值,生成page对象,由业务方负责按照逻辑编排返回值,之后呈现出JSON给业务方。
三、前后端一体化模式的进一步探索
上述方案基于统一的API,存在以下问题:高耦合API,业务数据和页面结构在同一个接口;传统JSON接口方案;前后端依旧有分治的思想(客户端所见即所得,服务端根据数据源组织Function)。接下来的演进方向,首先是接口拆分,分离页面结构和业务数据,第二是不感知数据协议,数据协议隐藏在框架,第三是后端作为MVVM整体的一部分,前后端响应式同步。
Function1、Function2和Function3对应到页面Function,页面Function转化为Page对象,之后生成widget。页面Function到Page承担着中枢的作用,一次请求必须更新所有的模块Function,重复拉取占用流量并且带来更新不及时的问题,例如限时秒杀模块中,希望倒计时结束时刷新数据,期待结束时的数据已到达客户端。整体的耦合逻辑偏高,页面Function包含业务数据和布局描述。
首先拆分页面Function和模块Function,页面Function下发页面结构配置,模块数据集中在框架内完成逻辑。ViewModel订阅字段强类型模式,例如title=function3.model3.title,仅需要关心订阅的数据是什么,无需知道从何而来,即具体的模型类和属性。ViewModel到Function通过同步sync的推拉结合方式,需要的时候请求,某些时候服务端推送数据。模块Function存在不同的依赖,底层会执行Function的合并请求,可以按照模块编排Function,实现更彻底前后端一体化的融合。最后是widget(view)的生成,同步数据到ViewModel,ViewModel绑定widget,生成页面。以上是目前探索的方向,对FaaS的能力要求较高,不仅需要统一的API,还需要推拉结合的能力,目前仍在探索,实现真正的前后端一体化。
关注「淘系技术」微信公众号,一个有温度有内容的技术社区~