珊瑚海跨端解决方案及在移动端的布局动态化实践

原创
2022/08/25 11:31
阅读数 2.7K

1.背景和介绍

1.1 跨端动态化技术的现状和问题

1.2 珊瑚海介绍

1.3 与其他框架的对比

1.4 应用案例

2.珊瑚海技术方案

2.1 整体设计

2.2 D2X

2.3 开发工具

2.4 移动端引擎

2.5 后端支持

2.6 跨端

3.未来规划


01

背景和介绍

1.1 跨端动态化技术的现状和问题

随着移动互联网的飞速发展,移动端新技术和框架层出不穷,其中跨端和动态化是被提及比较多的两个特性,跨端实现了一套代码多端复用,意味着可以帮助开发提效;动态化让客户端可以不通过发版就能实现功能或界面的改变,提升了用户体验,降低了新功能推广的成本。

目前主流的H5、React Native、Flutter等框架都在跨端和动态性方面有各有优势和不足,也都具备较稳定成熟的版本迭代和社区生态支持。在实际的业务中,对跨端技术,我们通常会从这两个方面来考量:

  • 性能和动态化的平衡

从移动端技术的现状看,性能和动态化是相对的两个属性,性能最强的原生应用,动态化能力最弱;动态化能力最强的H5,性能也是最弱的。两者折中的方案,RN对性能、内存占用问题,使他无法用于首页、核心单页等强调用户体验的场景。

  • 接入和开发成本

而Flutter虽然性能强,也衍生出了一些跨端方案,但Flutter框架过重,对接入方会有不小的成本,在与混合工程对接、动态化、包大小、运行内存这些问题上一直被诟病。而且RN和Flutter在混合工程的接入上,由于内存占用,都只能是载体页级别的接入,无法对现有页面或列表做局部的跨端动态改造。

所以,58同城和安居客虽然已经将上述三种技术导入,但在一些对性能和动态化都有要求和对接入成本要求高的场景,现有技术并不能满足需求。

安居客房源单页

在2021年,房地产面临密集调控,各地方政府不时会出台各种管控政策,同样也需要作为头部平台的安居客首当其冲,会要求对房源单页按各地方要求进行各种整改,整改会指定样式和需要显示的内容,全国各地整改要求频繁,整改时间短,既不能通过预埋代码的方式覆盖全部整改需求,又不能频繁发布小版本影响用户体验。

58同城首页卡片需求

58同城 APP首页卡片模块,类型多、更新快,对动态化有较高要求。产品需要快速验证 UI 效果,运营需要经常上线各种活动。

上述场景,对性能(首页和业务核心页)、接入成本(局部快速接入、发布更新频繁)都有要求,现有跨端技术无法满足。当然中间我们也做过一些尝试,比如基于 Android App Bundle + Qigsaw 的任意门、Mocha,实现了基于版本级别的线上 AB 测能力:

业务线接入情况:无线、招聘、部落、二手车
使用情况:任意门进行 bug 修复 3次,AB 测 6次
动态包覆盖速度:动态包安装量 7天 能覆盖活跃用户的 90% 以上

任意门、Mocha 性能与原生无异,具备彻底地动态化能力 (dex、so、资源),但其存在一些限制,特别是在纯布局动态化场景下:

  • 只支持 Android

  • 基于版本级别,PM 如果想跨版本连续验证效果,则需要打包、上线多次

1.2 珊瑚海介绍

CoralSea官网: https://coralsea.58corp.com/coralsea-docs/

珊瑚海是安居客发起,58无线团队参与共建的跨端解决方案,目标是打造一套在开发、接入和运行维度都能够高效且在大前端都能够广泛支持的跨端技术。

项目包含了跨端DSL标准、引擎框架、项目管理和动态下发后台、可拖拽低代码前端、 DSL编辑器等全链路体系;已经建设了Android、iOS、H5、小程序的跨端引擎;能力方面,支持动态化布局和数据绑定,基础的逻辑、时间和生命周期处理能力,支持页面、局部模块和列表卡片的使用场景。

总体适用于 UI 交互、动画复杂性较低、动态化和接入成本要求高的页面。目前在安居客、58房产的客户端/H5/PC/小程序的业务上已经大量使用。

安居客:

【移动端】邢智健(项目负责人)、钱杰、秦小杰、黄继业、吴战胜、尹余负责移动端引擎,D2X-Tool编辑器开发,D2X组件开发
【前端】高飞宇、杨萌,负责低代码前端开发,H5 引擎,组件库设计;祝求智,负责小程序引擎;王海君,负责前端D2X组件开发
【后端】张祖金、胡侃,黄柔星,负责后端接口开发

无线:
【移动端】况众文,吴振,负责移动端卡片、静态编译方式的设计与开发,负责管理后台的原型设计、接口设计、开发工作,参与低代码前端的共建等

  • 元数据驱动

通过数据驱动页面渲染

  • 跨端跨栈

基于统一协议,可以运行在Android、iOS、小程序、H5等主流端和技术栈,可灵活拓展

  • 动态化

轻量灵活的动态化热更新机制

  • 轻量易接入

基于DSL布局动态化引擎,接入和拓展成本低,对性能影响小

  • 低代码

服务于产运,低门槛快速产出页面

  • 一站式平台

提供设计-物料-开发-发布一站式的基础设施,满足工程化需要


1.3 与其他框架的对比

1.3.1 布局动态化你要理解的一些概念

前面介绍过,目前具备跨端动态化的框架已经很多,每种框架因为实现原理的差异,在动态性、多端一致性、性能、包大小等方面都有自己的优势和不足,不同公司、不同业务、不同时期在这些框架的选择上都存在不同。

主流的跨端框架大多过重,无论是 H5 还是 RN、Flutter 类的方案,它们动态化能力的关键在于随时可发布代码,但只是面向开发的动态化方案,发布代码意味着开发、测试、灰度等一系列流程都要完整跑通,显然难以满足产品和运营的诉求。

同时在现有工程中使用,对于包大小、接入成本、性能等有着严格地限制,RN 当前的版本依托于 Bridge,无法用于首页 (最新版使用 JSI、Hermes,但内存占用是问题),Flutter 与混合工程对接、动态化、包大小一直被诟病,H5 性能更差,js执行、栅格化绘制流程和交互刷新的漫长流程,三者共同作用,而移动端性能本身不足,更慢的让人抓狂。

我们将珊瑚海跨端解决方案应用在了移动端,它为移动端带来了跨端布局动态化,又足够轻量,接入成本低,使用场景广,对性能的影响也小。

1.3.2 布局动态化框架需要具备哪些能力?

  • DSL 布局描述文件

GPL(General Purpose Language),即通用编程语言,他的特点是语法全面,可以支持复杂逻辑,并且作为通用语言,使用和学习成本相对低,同时需要特定的编译器或解释器。H5 和 RN 为了追求开发模式的完备,各自使用了一套 GPL,比如 JS和 JSX,可以支持复杂的逻辑和动态效果,缺点是解析和渲染引擎复杂,实现成本高。

DSL(domain-specific language),即领域特定语言,他的特点是语法简单直观,便于解析,较方便描述布局,但逻辑表达能力较弱,如json, xml, ProtoBuf。优点是解析器实现较简单,性能也不会有过多损耗,有利于跨端能力的拓展和布局动态化框架轻量级的定位。同时珊瑚海在具备布局能力的同时,也拓展出了DSL下基础的逻辑、数据绑定能力,可以满足基本的业务需求。

  • DSL编辑工具

用于编辑产出 DSL。珊瑚海既有面向开发编码实现的工具D2X-Tool,也有能够有面向产运可拖拽的前端低代码平台,既提高了开发效率,又降低了开发门槛。

  • 管理后台

用于产品管理、DSL 管理,可快速编辑、发布上线。

  • 端侧引擎

提供跨端能力的基础,珊瑚海目前提供了基于D2X的Android/iOS/H5/小程序引擎,包含下载、解析、运行 DSL,以及基础组件(包含Widget、表达式)。

  • 可扩展能力

可由业务方自定义的能力,珊瑚海的可以自由拓展业务组件,满足业务需求。

1.3.3 布局动态化框架间的对比

Tangram、VirtualView

Tangram、VirtualView 是业内比较知名的布局动态化框架,同时其具备其他框架没有的组件动态更新能力,所以这边和它对比一下。

优点:

  • 跨端支持:支持

  • 性能:性能优,底层 C 实现,同时具备扁平化能力

  • 组件与动态能力:内置布局、组件丰富,开放的 API,同时 VirtualView 具备动态更新组件的能力

缺点:

  • 更新维护:最新更新在 2020/12

  • DSL 前端、管理后台未开源

珊瑚海

除了不支持组件的动态更新之外,在跨端支持、性能、内置的布局组件等方面和 Tangram 相差不大。同时属于内部团队持续维护,具备全套的开发组件 (可拖拽低代码前端、管理后台)

分别查看 Native 原生、Yoga、Tangram 在同一列表样式中的 fps 数据:

丢帧数和丢帧率结果为:

Tangram(3.87%) > Native(2.30%) > Yoga(1.43%)

Native 丢帧率比 Yoga 高的原因为 Native 布局编写层级过深导致,无扁平化处理,在布局、测量方面有性能丢失,合理的布局优化手段可以减少丢帧率。

再对比一下 Native 原生、Yoga、Tangram 在同一列表样式中过度绘制数据:

可以看到 Yoga、Tangram 因为都做了扁平化处理,所以在过度绘制方面表现不错。

1.3.4 混合开发中在原生列表上的表现

与 native 混合开发时,在列表滑动性能上面的表现:

1.3.5 珊瑚海对比 RN

珊瑚海无法与 RN 这种大型成熟的框架做对比,两者的侧重点和使用场景也不同,不过都是基于 Yoga 进行绘制,在这边做一下简单的对比:

1.4 应用案例

1.4.1 58同城预接入效果

DSL 管理后台 + 低代码前端:

App:

描边卡片为珊瑚海卡片:

1.4.2 安居客部分落地业务效果

珊瑚海房价卡片页面

  • 一套DSL跨Android/iOS,跨58同城/安居客,开发成本节省 50%。

  • 实现了RN/Flutter无法具备的模块化热更新能力,助力产品应对监管要求。

房产909项目经纪人门店单页

  • 使用珊瑚海 + BFF,体验与原生一致,上线以来热更新5次

整改需求- 删除当前APP的服务痕迹

  • 跨 Android & iOS,表单展示类页面,0.5人/日快速开发完成

1.4.3 移动经纪人落地项目

  • 珊瑚海业务也正在拓展到房产B端移动经纪人APP,第一个接入项目是神奇分业务,该业务处于上线初期快速迭代阶段,通过珊瑚海提供的动态热更新能力,可以帮助产品通过运营数据快速迭代,使新功能更快触达用户,提升业务效果。


1.4.4 后期落地计划


02

珊瑚海技术方案简介

2.1 整体设计

珊瑚海主要模块和开发流如图所示,我们搭建了一套围绕大前端跨端跨栈的整体开发流,形成一套整体解决方案。包含了客户端引擎、珊瑚海DSL生成工具以及配套的后端管理和数据BFF平台。通过左侧的工程化支持,生成通用的DSL,为大前端的引擎提供跨端动态化的开发提效支持。

2.2 D2X

珊瑚海的核心思想是以元数据驱动来驱动客户端跨端动态化的UI界面。

元数据一般通过领域特定语言(DSL)或通用编程语言(GPL)的形式来描述:

珊瑚海选择了自定义DSL的方式作为开发语言,这套自定义的DSL我们称之为D2X(Dynamic to X),顾名思义,这套D2X需要满足两个能力要求:

  • 动态性, 具备动态数据绑定,逻辑运算能力

  • 跨端, 布局、样式能够尽可能在各端兼容

基于这两个要求,我们把业务需要的常用能力抽象成了以下两类D2X语法:

  • Widget(组件)描绘了珊瑚海UI

基础组件:Text,Image,View,List,ViewPager,Span

业务方可自由扩展业务组件

组件具备以下几类属性:

Layout(布局, 遵从Flex布局)

Style(样式, 遵从CSS标准)

Props(自有属性)

Event(事件)

  • Expression(表达式)实现了珊瑚海的动态特性

Logic(逻辑)

Action(行为)

API(接口数据绑定)

Store(状态数据绑定)

从而既符合标准,便于跨端,又具备了动态化数据绑定和一定的逻辑能力,能够满足常见的不包含复杂交互和动画的需求。

部分代码示例:

{  //API和本地变量定义  "state": {    "api": {      "sqf": {        "name": "sqf",        "method": "GET",        "url": "https://api.anjuke.com/mobile-ajk-broker/3.0/packall/coralsea-bffcustom/broker/getBrokerExtend"      }    },    "store": {        "hideCardStyle1": "true",        "hideCardStyle2": "true",        "hideCardStyle3": "true"    }  },  //生命周期  "lifeCycle": {    "onCreate": [      {        //Action表达式 请求API        "interfaceType": "Expression",        "type": "Action",        "value": "InvokeApi",        "params": [          {            "interfaceType": "Expression",            "type": "JSON",            "value": {              "name": "sqf"            }          }        ],        "extra": {          //成功回调          "onSuccess": [            //Action表达式 发码            {              "interfaceType": "Expression",              "type": "Action",              "value": "SendLog",              "params": [                {                  "type": "JSON",                  "value": {                    "logId": [                      //API表达式 数据绑定                      {                        "interfaceType": "Expression",                        "type": "API",                        "value": "sqf.data.score.cards[0].sendLog.onAppear"                      }                    ]                  }                }              ]            }          ]        }      }    ]  },  //布局开始  "node": {    "id": "RQRxyZiE7imzxfak",    "name": "View",    "className": "Container",    "child": [      {        "id": "jaxi3JdC3hxmkc2S",        "name": "View",        "className": "Column",        "child": [          {            "id": "WTeHWx4TjZzNExMd",            "name": "View",            "className": "Container",            "child": [              {                "id": "WHe4W8JiXBSxxj2h",                "name": "View",                "className": "Row",                "child": [                  {                    "id": "kNGRmnm5QMJmsyRD",                    "name": "Image",                    "className": "Image.network, GestureDetector, GestureDetector",                    "layout": {                      "width": "54px",                      "height": "54px"                    },                    "style": {                      "display": "flex",                      "borderRadius": "28px",                      "borderWidth": "1px",                      "borderColor": "#f0f0f0"                    },                    "props": {                      "url": [                        //Store表达式 数据绑定本地存储                        {                          "interfaceType": "Expression",                          "type": "Store",                          "value": "Local.userAvatar"                        }                      ],                      "scaleType": "aspectFill"                    },                    //事件处理                    "event": [                      {                        "name": "onClick",                        "action": [                          {                            //Action表达式 跳转                            "interfaceType": "Expression",                            "type": "Action",                            "value": "Jump",                            "params": [                              {                                "type": "String",                                "value": "openbroker2://vip.anjuke.com/mine/brokerinfo_act"                              }                            ]                          }                        ]                      }                    ]                  },   ...                }

2.3 开发提效

珊瑚海D2X,是一套自定义的DSL,并且基于JSON描述,JSON适合作为数据传输和解析的介质,其不适合直接开发。所以珊瑚海设计了低代码编辑平台和D2X-Tool来满足不同用户的开发需要。

2.3.1 低代码前端

珊瑚海低代码平台是以面向产运为目标的珊瑚海开发平台,提供可视化的拖拽编辑能力,以降低珊瑚海使用门槛,同时融入珊瑚海后端管理平台,提供一站式的开发和发布体验。

架构设计

其中低代码平台负责:

  • 加载组件库

  • 拖拽、鼠标事件

  • canvas 绘制(布局、位置、margin、padding)

  • 代码编辑

  • 操作历史记录

  • 模版管理

  • 生成 dsl

组件库负责:

  • 拥有哪些组件,每个组件的属性、绘制,和端侧引擎同步迭代

功能拆解

2.3.2 D2X-Tool

珊瑚海既有主要面向非技术人员和轻量级需求开发的低代码平台,也同时提供了一套为技术人员打造的能够满足复杂业务和多人协同的专业级开发工具,它就是D2X-Tool。

具备以下能力:

  • 主流技术栈,适用环境广泛

基于TypeScript、node等主流技术栈,无论在开发侧本地还是前端服务器上都可以运行,也兼容低代平台的运行环境。

  • Widget与Flutter对齐,上手门槛低

采用相应式UI开发方式,珊瑚海Widget的布局、样式和属性都与Flutter对齐,有Flutter开发经验的同学可以快速上手。

  • 高效的D2X开发支持

封装了珊瑚海D2X的Action和表达式语法糖,更符合开发习惯,可读性更好,相比D2X原始代码,能够减少80%左右的代码量。

  • 支持复杂业务需求

业务代码可灵活拆分为模块和依赖库,解决了在复杂业务场景下D2X难以维护的问题。

  • 完善的工程化能力

融入通用技术栈的好处是可以直接使用现有成熟的git代码管理,打包平台,依赖库发布等工程化能力,快速形成稳定的生产力。


2.3.3 D2X组件库

组件化的思想,也在珊瑚海中有应用,对于复用度高、包含逻辑复杂的常用组件,我们通过珊瑚海D2X组件,来进一步提效。

为了保留D2X原有的跨端能力,并减少客户端的兼容成本,我们做了如下设计:

如图所示,D2X组件库是devDependency的,只在开发中可见,D2X组件库中包含对应组件的编译方法,通过编译生成的D2X产物只包含Atom组件,对客户端引擎来说D2X组件库带来的影响是透明的,只要在低代码和D2X-Tool兼容即可,无需额外改造成本。

D2X组件接入实例:

如上图所示,是移动经纪人房源发布页接入了D2X组件库的例子

该页面是一个较为复杂的表单页,包含表单提交字段检查、报错处理等复杂逻辑,以及滚轮选择等逻辑处理,以及滚轮选择、多行输入框等组件。借助D2X组件,业务代码在D2X-Tool开发环境下,仅需要200多行ts代码,就等效于编译出的2600多行D2X json端,代码量缩减到10%以下,并且可读性和可维护性大大提升。


2.4 移动端引擎

2.4.1 移动端引擎介绍

布局

基于 Flexbox 语法(弹性布局,跨端),Yoga(RN 使用的渲染引擎)渲染,高性能,底层 C++;扁平化

数据绑定

⾃定义 Action

注册到 ActionManager,继承 BaseAction,注册/静态类⽅法

逻辑动态

珊瑚海SDK本身支持JSONLogic轻量级逻辑解析器,如果不能满足需求,可以通过Action: JSEval(script, params…)调用轻量级引擎QuickJS(Android)或内置引擎JSCore(iOS),来实现复杂逻辑。

2.4.2 全新的移动端 UI 开发模式

传统的移动端开发 UI 一般分为以下几步:

从流程中可以看出,传统的开发模式存在以下几个问题:

  • Android & iOS 需要对一份 UI 设计在对应平台分别进行开发

  • 单平台开发时,也存在重复输入,每一个 UI 都需要对应编写视图描述文件、数据实体、数据解析、适配器绑定 UI 数据等逻辑

  • 使用布局动态化开发 UI 的方案优点:

  • 跨端支持,支持 Android & iOS,同一份 UI 只需要编写一份 DSL 文件即可在两端运行

  • DSL 文件支持数据绑定能力,无需重复编写视图查找、数据实体、数据解析、绑定的代码

  • 如果不考虑动态更新问题,也可以将 DSL 文件内置在移动端本地来解决本文章提到的问题

但在动态布局方案中,强调的更多是布局的动态化,其加载、解析 DSL 存在部分耗时:

以 500行的 DSL 文件为例,在中高端手机运行时加载 + 解析文件耗时达到 70ms(其实这个过程和原生相差不大,原生的优势在于系统 AOT 的加持)

如果不考虑动态化,只是作为内置型应用场景,如何优化此部分性能呢?

编译期提前将 DSL 文件转换成原生代码,省去加载、解析的时间,同时又可以更多地享受系统 AOT 的加持,缺点就是 dex 变大, 加长 classloader 加载时间

这边转换的是只是 DSL 对应的 JSON 对象的代码,而没有转换 JSON 对应的每个视图的代码,主要是因为更灵活,无需重复编写 DSL 运行时解析视图的代码,且 DSL 解析成 JSON 对象后,对应的构建 View 等操作和原生差异不大。

实验数据,同样的 DSL 文件,JIT 模式优化到 8ms, AOT 模式优化到 1ms.生成的代码示例:

class RevertToJsonTest {
/** * output: * {"node":{"layout":{"alignItems":"center","flexDirection":"column","width":"100%","justifyContent":"center","height":"100%"},"name":"View","style":{"backgroundColor":"#ffffff"},"id":"700-003"},"api":[{"method":"GET","name":"communityInfo","url":"https://api.anjuke.com/community/info"}],"lifeCycle":{"onCreate":[]}} * * Test on mobile: load and parse json 80ms, revert map to json: JIT 6ms, AOT 1ms, save 92% - 98% */ public static void main(String[] args) { long startTime = System.currentTimeMillis();
Map<String, Object> resultMap = new HashMap<>(); Map<String, Object> node17645087271206Map = new HashMap<>(); resultMap.put("node", node17645087271206Map); Map<String, Object> layout17645087328278Map = new HashMap<>(); node17645087271206Map.put("layout", layout17645087328278Map); layout17645087328278Map.put("alignItems", "center"); .... _17645091636381Map.put("style", style17645091785331Map); style17645091785331Map.put("color", "#0B0F12"); .... _17645091636381Map.put("id", "700-00244"); _17645093309193Map.put("layout", layout17645093317330Map); layout17645093317330Map.put("marginRight", "16"); .... _17645093309193Map.put("name", "Text"); Map<String, Object> style17645093400202Map = new HashMap<>(); _17645093309193Map.put("style", style17645093400202Map); style17645093400202Map.put("color", "#0B0F12"); style17645093400202Map.put("fontSize", "16"); _17645093309193Map.put("id", "700-00389"); Map<String, Object> props17645093441338Map = new HashMap<>(); _17645093309193Map.put("props", props17645093441338Map); props17645093441338Map.put("text", "区别于组件,模版指的是可复用的DSL代码段,是现有DSL组件能力的组合和复用"); Map<String, Object> _17645093462008Map = new HashMap<>(); child17645090142087Array[14] = _17645093462008Map; Map<String, Object> layout17645093470302Map = new HashMap<>(); _17645093462008Map.put("layout", layout17645093470302Map); layout17645093470302Map.put("marginRight", "16"); .... _17645093462008Map.put("name", "Text"); Map<String, Object> style17645093573580Map = new HashMap<>(); _17645093462008Map.put("style", style17645093573580Map); style17645093573580Map.put("color", "#0B0F12"); .... _17645093462008Map.put("id", "700-00408"); Map<String, Object> props17645093625570Map = new HashMap<>(); _17645093462008Map.put("props", props17645093625570Map); props17645093625570Map.put("text", "珊瑚海Bundle DSL描述文件"); Map<String, Object> _17645093647561Map = new HashMap<>(); child17645090142087Array[15] = _17645093647561Map; Map<String, Object> layout17645093656085Map = new HashMap<>(); _17645093647561Map.put("layout", layout17645093656085Map); .... _17645093647561Map.put("name", "Text"); Map<String, Object> style17645093736060Map = new HashMap<>(); _17645093647561Map.put("style", style17645093736060Map); style17645093736060Map.put("color", "#0B0F12"); style17645093736060Map.put("fontSize", "16"); _17645093647561Map.put("id", "700-00430"); Map<String, Object> props17645093779067Map = new HashMap<>(); _17645093647561Map.put("props", props17645093779067Map); props17645093779067Map.put("text", "指的是包含珊瑚海Bundle DSL描述、版本、依赖视图组件版本等信息的JSON文件,需要随珊瑚海Bundle DSL一起上传平台"); Map<String, Object> _17645093807173Map = new HashMap<>(); child17645090142087Array[16] = _17645093807173Map; Map<String, Object> layout17645093816175Map = new HashMap<>(); _17645093807173Map.put("layout", layout17645093816175Map); layout17645093816175Map.put("marginRight", "16"); .... _17645093807173Map.put("name", "Text"); Map<String, Object> style17645093952708Map = new HashMap<>(); _17645093807173Map.put("style", style17645093952708Map); style17645093952708Map.put("color", "#0B0F12"); style17645093952708Map.put("fontSize", "16"); style17645093952708Map.put("fontWeight", "bold"); _17645093807173Map.put("id", "700-00449"); Map<String, Object> props17645094058650Map = new HashMap<>(); _17645093807173Map.put("props", props17645094058650Map); props17645094058650Map.put("text", "珊瑚海Bundle DSL版本映射表"); resultMap.put("lifeCycle", lifeCycle17645094899247Map); Object[] onCreate17645094910216Array = new Object[0]; lifeCycle17645094899247Map.put("onCreate", onCreate17645094910216Array);
// new JSONObject by map JSONObject jsonObject = new JSONObject(resultMap);
System.out.println("RevertToJsonTest revert cost time: " + (System.currentTimeMillis() - startTime)); System.out.println("map revert to json: rn: " + jsonObject.toJSONString()); }}

2.5 后端支持

2.5.1 管理平台

- 一站式全链路闭环

平台管理 -> 低代码前端 -> 各端引擎,一站式开发部署上线

多平台、多端

打通低代码前端

快速上线发布

业务服务可配

各业务可接入管理平台 API,部署自己的 DSL 服务,保证高流量、高并发请求。

一份 DSL,多端复用

基于 DSL 文件级别的复用,项目管理分开,做到复用的核心为多端引擎的对齐,引擎的向下兼容性。

优点:

  • 移动端、小程序、h5 版本节奏不一致,分开管理灵活度高。出现问题方便回滚,不会牵一发动全身。

  • 平台版本、引擎版本、DSL 版本逻辑复杂,揉合在一起系统会非常复杂难以管理。这样设计同时可减少后端复杂度,出现问题易排查。

  • 后台预留了直接上传的入口,开发工作量可节省。

缺点:增加 DSL 管理、上线的工作量。

插拔式组件库

插拔式组件库,严苛地版本管理

业务可以定制自己的组件库,并进行版本迭代,在编辑 DSL 文件时,会根据版本规则匹配可用的组件库:

低代码前端动态加载对应组件库:

共建共享型模版

类比应用市场主题,大家可以分享自己的 DSL 模版,一起快速构建美观的视图

2.5.2 BFF

BFF (Backends For Frontends) 服务于前端的后端,是后端各种微服务、API之间的一层胶水代码。珊瑚海引入BFF,具有以下优势:

  • 将部分前端逻辑从D2X移入BFF,下发代码所见即所得,降低D2X代码复杂度,弥补DSL逻辑能力的不足

  • 聚合接口,客户端一个页面请求一个接口,提升整体性能

  • 客户端编写BFF代码,后端仅提供元数据,专注于性能优化而不需要深入理解业务,减少了额外的沟通理解成本,从流程上实现提效

在技术选择上,我们使用node来搭建BFF服务,对前端和客户端开发来说门槛更低,架构如图所示:


2.6 跨端

珊瑚海目前已经在移动端有一定成果,也实现了业务上的承载,后续的发展方向在哪里?

一个重要的方向就是跨端

目前大前端的新技术层出不穷,带来的问题就是需要享受新技术红利的同时,也会付出相应的成本,比如果要用到基于Flutter的动态化技术,前端开发需要学习Dart语言,后端需要搭建Flutter的动态下发服务;升级RN的版本,会导致以前老的RN业务代码不兼容,也需要升级。而随着App里存在的框架越来越多,对后续平台的维护、兼容性的管理都是比较大的挑战。

在移动端实现的基础上,我们设计了珊瑚海的跨端架构,如图所示:

使珊瑚海成为架构在大前端各端各平台的通用框架,为个平台赋能,如图所示,在各端各平台底层技术栈和上层业务之间增加珊瑚海中间层,来实现业务与底层的解藕。

带来的优势主要有:

  • 根据业务抽象出的元数据DSL ,多端通用,不受底层技术栈影响

  • 可以为底层技术栈(如Native、Flutter)提供统一的动态化能力

  • 所有端接入后都可以共享珊瑚海的工程化基建,不用重复造轮子,提供一站式的从开发到上线的能力

  • 珊瑚海不侵入底层框架,升级/替换底层技术栈不影响现有业务,在遵循DSL协议的基础上,接入新的技术栈即可让现有业务获得新的跨端能力

除了Android/iOS,珊瑚海目前的Web和小程序引擎也已经开发完毕,基于同样的珊瑚海D2X标准,能以较低的成本实现跨端。目前已应用在了安居客CMS、活动页等场景。具体的技术细节可以等待项目组后续同学的分享。


03

未来规划

珊瑚海目前已经在安居客/58同城 App上跑通了全流程,也经受了大流量线上业务的考验,体现了在跨端/动态化方面的能力。跨端提效仍然是我们的初心,我们的后续计划也继续围绕着这两点进行:

提升开发体验,优化开发工具

  • 进一步完善珊瑚海开发工具低代码/D2X-Tool的开发体验

  • 继续完善珊瑚海的工程化体系,包括多页面、多卡片的项目管理,一体化的打包发布流程

  • dedug能力加强,开发工具与真机联动

跨端能力持续建设

  • D2X组件库建设,提高开发效率

  • 小程序引擎实际项目落地

待项目进一步完善后,我们也计划开源,欢迎继续关注珊瑚海项目以及项目组的后续分享。

作者简介

邢智健:安居客房产技术部-无线平台技术部-移动端资深开发工程师
况众文:58同城-增长技术部-移动端资深开发工程师

本文分享自微信公众号 - 58技术(architects_58)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部