订单业务楼层化 view管理器和model管理器进行了model和view的全面封装处理 三端不得不在每个业务入口上线时约定好降级开关,于是代码中充满了各种各样的降级开关字段
京东APP订单业务楼层化技术实践解密
用户体验关键影响因素
京东订单业务,主要负责为京东用户提供下单后订单的信息全面展现,用户在下单后会不断关注自己的订单状态变化,还有可能在购买后的一段时间内查询历史订单,并进行商品的复购。订单业务承载着用户最为关注的物流信息、商品信息、价格信息等内容,因此业务体验可能会对引流、复购、用户粘性等造成重要影响。
订单业务中,订单详情页是一个核心页面,展示有以下关键信息:订单号、所购商品信息及件数、物流信息、下单日期、购买价格、服务中心等。客户端发展初期详情页展示不外乎以上内容,目前京东平台已拥有大量不同的业务类型,仅仅是基础信息的提供已经不能满足用户,用户需要持续化、个性化服务,各种订单类型需要定制化的业务入口为用户提供更贴心的服务,不同业务类型也需要有区别的进行展示。楼层化之前的订单详情页因存在以下诸多弊端已不能够跟随京东业务的拓展速度提供优质技术服务,技术上支持页面多样性势在必行。
版前订单详情页存在短板
开发不灵活 页面单一 数据冗余
开发不灵活
每次订单页面引进新业务需要三端共同开发并且必须跟随发版。随着业务类型的增加,各种新业务入口时常加入订单,老的页面数据结构与页面布局逻辑都较为死板,而新的业务入口常常需要交互设计师根据业务类型以及与用户的关联进行展示位置确认和样式设计,对于订单模块的三端开发来说,意味着服务端要新增数据结构在原结构中,同时也会影响原有的逻辑结构,客户端需要根据设计稿,在原逻辑中进行“插入型开发”。
2页面单一
页面布局基本固化,入口位置不变。由于页面布局不灵活,页面的展示形式无法根据不同的业务进行调整控制,例如商品信息与服务中心的位置,不管任何订单类型、订单状态都会是同样的布局形式。新业务也难以根据业务特色进行页面定制化展示。
3数据冗余
逻辑冗余叠加,数据结构日益庞大,维护成本高。数据全部在庞大的orderinfo数据结构中存储,数据语义及用途杂乱无章,存在严重的牵一发而动全身的潜在风险,且由于新业务入口上线会存在各种这样或那样的异常可能,开发新的业务入口,必须考虑降级方案的方式,由于原代码结构的特点,三端不得不在每个业务入口上线时约定好降级开关,于是代码中充满了各种各样的降级开关字段。
随着业务的扩展以及多样性,以上三个痛点问题在版本迭代中越来越痛,问题日益明显,拖着沉重的身躯自然无法轻便跟随上京东日益扩大的业务脚步,因此订单团队计划舍弃老框架,为新业务服务设计一个轻便灵活的数据结构和页面支撑。
核心改造目标
灵活
1、业务配置灵活:新增业务可灵活选择已有模板进行搭配,随时上线,无需等待发版
2、楼层展示灵活:楼层顺序可随需而动,亦可根据不同位置的曝光点击埋点数据规划最适合该内容的区域。
3、模板布局灵活:部分配置化楼层内部的展示区域可控,即坑位信息展示为文案或图片、色值、跳转与否均由服务端可控,提供定制化服务。
2流畅
业务灵活可配但不能影响页面流畅程度,减少页面卡顿与帧率问题,优化页面布局,控制OOM问题的产生。
3易维护
代码方案有一定的规则,例如楼层结构有数据字段规则、楼层样式规则、埋点规则、通用跳转规则、分割线规则等,所有楼层遵循这套规则进行数据下发。新增一个楼层信息,开发布局流程规范化、简化,且当出现问题时,查找问题也易查易改。
楼层化改造实践
1楼层划分
楼层划分意为将原页面的展示以区域横向通栏为单位做页面切割,切割区域可作为一个楼层进行页面搭配组合,商品楼层、店铺楼层、物流楼层、价格楼层等都可切分为一个楼层,如图所示:
在订单详情页老页面进行划分区域后,楼层类型又分为固定楼层和可配置化楼层。固定楼层用于页面常见的复用性低的常规楼层,例如:服务中心楼层;配置化楼层的坑位是可定制信息,字体颜色跳转等均可配置,用于复用率高的样式楼层,例如各种进度楼层。
2新数据结构设计
在设计楼层化数据结构时需要考虑一个问题,数据结构如何设计能够不冗余并且合理的支持数据发放?如果数据都在楼层结构中下发,那么将会有很多重复字段导致楼层数据结构过大;如果业务数据都放在一个map中,又会绕回老路导致越来越臃肿。最终经过讨论,制定的方案是,将楼层依赖度高、重用率低的数据放在楼层的业务结构floors中,将重用率高的字段放在统一业务数据结构others中。这样就实现了灵活又搭配方便的数据结构,对楼层数据进行“精准”下发,下发的数据结构只需两部分:floors和others即可。这样的设计在后续业务新增时,可以很容易根据数据属性和用处的不同决定将数据下发到哪个节点中更合适。
3页面搭建
1、服务器数据结构的搭建过程
①首先在模板系统中进行模板选型,若所需要的模板样式已存在则可直接复用该模板,若是该楼层样式对应模板不存在,则新建一个,最终会生产一个模板ID,作为与客户端楼层协议的标识,说白了就是客户端拿到这个模板ID,然后展示不同的楼层样式。
②在业务系统中新建一个模板实现类,实现已有的统一接口FloorHandler,同时实现模板方法,isValid()前置判断该楼层业务展示条件,onEvent()进行具体的业务逻辑处理,封装下发数据。
③将该类注册到配置系统,与模板ID进行映射对照,同时借助zookeepr监听通知机制通知业务系统加载该楼层实现,执行模板方法下发数据;同时还可以做到配置化动态插拔。
根据上述过程描述,楼层模板可以复用,减少依赖发版次数;每个楼层实现类只处理自己的业务逻辑,楼层间数据也是相互隔离,满足单一职责原则;新增楼层只需要新加一个楼层实现类即可,不会对原有数据和逻辑产生影响,满足开闭原则;通过配置注册信息,实现动态插拔,通过这一系列的手段组合,最终使得系统可维护性得到很大提高。
2、客户端结构设计
为了达到页面性能流畅以及楼层开发的易维护性,客户端的结构设计如下:
①业务和UI分界清晰,可以看到,框架中ViewManager中的各个楼层view各自管理UI,楼层基类提供所有楼层所需要的公共方法,如分割线设置、楼层重载、暗黑设置等;也提供了父类公共方法供子类重写以适应不同楼层需求,例如布局方法和楼层高度获取方法。
业务功能模块和工具模块则为页面所有的楼层和主容器提供业务接口和工具方法接口。业务功能主要负责提供业务调用接口,方法内部实现业务处理代码,工具模块负责提供整个页面的公共工具方法,例如电话号码正则处理、异常弹窗处理等。楼层基类提供了代理,通过Cell容器传递到页面容器VC中,代理中核心方法是一个处理业务逻辑的方法,通过自定义枚举区分要处理的业务逻辑,在页面容器实现代理时根据枚举值判断区分业务的调用。
如上开发页面样式的时候完全隔绝了业务逻辑的开发,并且通用的业务逻辑可以做到为所有楼层提供接口服务,新增业务逻辑也变得简单,排查业务逻辑问题也无需在所有代码中乱找。
②快速搭建楼层
在设计过程中,如何减少未来增加新楼层的开发成本是我们考虑到的问题,如果每增加一个楼层都需要客户端在容器逻辑、model、view中分别开发就不符合楼层化的灵活和易维护目标。为了解决这个问题,我们分别用一个view管理器和model管理器进行了model和view的全面封装处理。现在看一下ModelManager[以下简称MM]和 ViewManager[以下简称VM]如何为页面容器工作提供数据模型model和楼层view,以及VM提供的楼层数组是如何拼接在页面中的。
服务器和客户端对于楼层有约定好的唯一标识字段mid,客户端在MM和VM都存放了一个映射字典,其中key是楼层的mid,value是该楼层的类名字符串。这个映射字典是创建类的关键,网络请求拿到服务端数据后,容器调用MM的解析方法根据楼层ID从映射中查找对应的model类名字符串,通过类的动态加载方法创建类,创建后的类放入model数组。如下iOS中类动态加载方法:
同理,VM通过容器VC拿到model数组后也会根据model中的楼层唯一标识,通过映射表获得对应楼层的类名,用类的动态加载方法创建楼层view类,创建的楼层实例调用楼层基类的基础布局方法进行布局,就这样布局好的view放入VM的数组中,提供给容器进行布局使用,楼层的高度则通过各个楼层重写基类的高度方法进行获取。
正常流程下原生页面开发在增加页面信息的时候,需要面临的过程可能是这样的:
但是按照目前的设计结构,假如新增一个楼层,客户端涉及到的开发流程简化为下图:
如图所示仅仅需要创建一个楼层model和view,然后按照和服务端约定的楼层id进行注册,即可专心于楼层view开发,调用已有业务逻辑或者新增一个业务枚举和逻辑处理后进行调试即可,和其他楼层的布局内容完全不耦合。在订单详情页中新增一个新的业务流程大大的减少了开发的成本和维护成本。即使新人上手,也可通过规则很快的进入复杂的业务开发中。
③性能的优化
客户端在处理的时候会进行“预计算”,在页面展示出来之前,已经通过数据对楼层进行计算,内容的位置以及楼层高度都无需在页面展示布局的时候进行;客户端会在页面布局前,对楼层提前进行预布局,在页面展示的时候,楼层其实都已经布局完毕了,直接拿过来“搭积木”就可以;可复用的楼层会在刷新页面的时候,进行缓存和复用,减少页面内存开支,配合楼层化结构以控制内存方面的开支。
配置化能力大幅度提升
订单详情页楼层化改版上线以来,所具备的配置化能力为订单的技术服务效率做了提升,能够快速支持部分紧急业务的配置,所支持的配置化需求占比可高达40%,大大节省人力资源和时间成本。目前订单模块的配置化能力也在逐步增强,订单技术团队也将从不同的角度出发挖掘更多可扩展服务,通过持续性的优化和新技术的探索引进继续提效,为用户提供更便捷的页面服务。