引入 CloudWeGo 后飞书管理后台平台化改造的演进史

案例介绍

feishu


飞书管理后台是飞书套件专为企业管理员提供的信息管理平台,在单体应用架构下,它面临了一系列的挑战。 它通过引入 Kitex 泛化调用对飞书管理后台进行平台化改造,使之变为业务网关,提供一套统一的标准和通用服务,让有管控诉求的套件业务方能快速实现能力集成,并且提供一致的体验。最终实现了飞书管理后台作为企业统一数字化管理平台的愿景。

本文将从三个方面为大家讲解 Kitex 泛化调用在飞书管理后台平台化改造过程中的落地实践:

  1. 架构和挑战,即飞书管理后台单体架构面临的各种挑战;
  2. 平台化构想,即飞书管理后台平台化构想和架构升级;
  3. 平台化实现,包括微前端技术架构、泛化调用实践和功能扩展。

架构和挑战

飞书是真正的一站式企业沟通与协作平台,整合视频会议、即时消息、日历、云文档、邮箱、工作台等功能于一体,立志打造高效的办公方式,加速企业成长。 飞书管理后台(以下简称 Admin)是飞书套件专为企业管理员提供的信息管理平台,企业管理员可通过后台管理企业设置、组织架构、工作台和会议室等功能。下图是飞书管理后台的界面。

feishu1

平台改造背景

飞书采用的是 all-in-one 的套件模式,Admin 作为整个套件统一的管理后台,承接了包括组织管理、云文档、视频会议、邮箱、开放平台等 10 多个业务线的管控需求。 一直以来的开发模式是各业务方直接在 Admin 的代码仓库提交代码或者由 Admin 团队负责 Web 层逻辑的开发。下图是目前飞书管理后台中包括的一些功能, 可以看到功能种类还是非常多的,之前的开发模式是业务方直接在 Admin 的代码仓库中提交代码,或者由业务方给 Admin 团队提供一些需求,由我们来负责 Web 层逻辑的开发。 从飞书初创开始,Admin 就是以单体应用的模式开发的,随着后续飞书整体的演进,我们的团队越来越多,不同业务线的团队也会有一些管控需求要接入 Admin 平台,因此他们就直接在代码仓库中提交代码。

feishu2

Admin 架构

下图是 Admin 旧架构图。上面是 Admin 前端,它其实就是 Node 层的单体,中间是 Admin 后端,它基于我们内部单体 HTTP 的 Web 服务,会通过 RPC 调用到其他业务线的微服务。

feishu3

面临的问题

在这个架构下我们会面临一些问题,第一个问题是业务迭代慢,因为所有业务线都只能在 Admin 的代码仓库里进行开发和发版,因此这些业务线完全依赖 Admin 的研发资源和迭代流程, Admin 的研发资源被过多的耗在各业务的迭代中,无法快速支持自身的业务规划,如组织架构、安全、KA等因为我们是 To B 的产品,因此发版节奏不会很快。 如果各个业务线有一些比较紧急的需求,也只能跟 Admin 的节奏,这就会造成发版节奏不一致,研发资源不匹配,导致Admin会成为业务迭代的瓶颈。 第二个问题是研发效率低,因为各个业务线需要在 Admin 的代码仓库里进行修改,因此需要了解我们仓库的设计模式,我们在为各个业务线提供服务时,也需要了解各业务的上下文,双方都需要花费大量时间沟通。 联调、Oncall的链路也很长,双方的责任也划不清楚,导致整体的研发效率偏低。第三个问题是工程质量差,多个团队共同维护一个代码仓库,代码质量参差不齐,设计规范也各不相同,底层的代码的修改还会相互影响,造成线上问题。

面临的挑战

此外,我们还会面临很多挑战。首先面临的是多环境互通与隔离的问题,我们需要解决不同环境的网络隔离、版本异构问题;其次是接入业务复杂性,Admin需要集成十几个业务线,接入诉求不统一。 接口协议包含 HTTP 协议和 Thrift 协议,还有各种自定义插件需求和权限校验需求;最后还有安全保障,Admin 作为飞书套件的管理配置中心,关系到整个企业的数据安全。 安全一直是 Admin 最重要的需求,为了保障Admin 的接口数据安全,需要提供鉴权中间件、管理员权限验证、参数校验、风控、频控等功能,提升业务方的安全能力。

平台化构想

第二部分给大家介绍飞书平台化构想,即如何进行飞书管理后台平台化构想和架构升级。

首先要明确的是目标,主要目标是通过提供一套统一的标准和通用服务,让有管控诉求的套件业务方能快速实现能力集成,并且能给客户带来一致的体验。 因为我们各个业务线的管控需求都是集成在 Admin,我们不希望每个业务线提供的 Web 页面展示、功能和 UI 等差别较大,希望他们是相对统一的,最终实现Admin作为企业统一数字化管理平台的愿景。 关于在技术上需要达到的效果,我们希望业务方不要继续在 Admin 代码仓库中进行代码开发,而是直接提供我们的后端接口和前端页面动态接入,Admin 无需代码改造和服务发布即可无缝上线,Admin 从单体应用进化为业务网关,是包含 UI 交互在内的独立产品模块的集成。

我们并不是要做一个搭建系统。目前很多平台型产品都提供 Low Code / No Code 的工具方便开发者快速搭建所需要的功能。 但是目前通过我们对客户诉求的调研,没有相关的需求(但是不代表未来也没有,这块我们会持续保持关注)。 我们需要做的是制定相关的标准,比如 UI、交互、API 等,业务按照标准去实现。我们也不是要做一个 API Gateway 或者 Service Mesh。 API Gateway 的核心是Exposes your services as managed APIs,将内部的服务以更加可控可管理的方式暴露出去,可以认为是后端服务的一个代理。 Service Mesh 可以看成是 API Gateway 的去中心化实现方式,用来解决单点、隔离、耦合等问题。我们需要解决的不仅仅是服务路由、协议转换、安全管控等问题,而是包含 UI 交互在内的独立产品模块的集成。

旧的框架

这是我们旧的架构。它的前端架构是前后端分离的 Node 单体项目。后端架构(Golang 实现)采用 Hertz 框架对前端暴露 HTTP 接口,Handler 层通过 Kitex 调用依赖的各个业务线的微服务。

feishu4

框架介绍

下面介绍一下 CloudWeGo 现有的两款框架。首先是 Hertz 框架,Hertz [həːts] 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 Fasthttp、Gin、Echo 的优势,并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用 Golang,如果对微服务性能有要求,又希望框架能够充分满足内部的可定制化需求,Hertz 会是一个不错的选择。Kitex [kaɪt’eks] 字节跳动内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的特点,在字节内部已广泛使用。如果对微服务性能有要求,又希望定制扩展融入自己的治理体系,可以考虑选择 Kitex。

新的框架

下面介绍一下我们的新架构,它主要包括:

  1. Gaia 控制面。我们增加了 Gaia 平台(基于 Hertz 框架的 Web 服务)来作为我们整个 Admin 的控制系统,负责整体的发布和管控需求,包括接口的生命周期管理、微应用生命周期管理、监控告警、业务线接入、多环境发布等。
  2. 前端架构。前端采用微前端架构,各个业务方通过构建微应用接入 Admin 基座,使用统一封装好的组件库实现前端页面。
  3. 后端架构。后端使用字节通用 BAM 规范,通过泛化调用的方式打通 Admin 和各接入业务方服务,并抽象公共组件以插件的方式进行功能扩展。

Admin 架构

下图就是新的 Admin 架构图。左上方是微前端,它包含前端里面各个业务线的微应用。微前端通过 HTTP 接口和 Admin 网关进行交互,Admin 网关把业务逻辑都剥离到下一层, 而自身只负责公共组件、登录鉴权、协议代理和通用配置等通用需求,同时它会通过泛化调用来调用下游的业务服务。业务服务包括组织管理、云文档、视频会议和邮箱等微服务。 右侧是 Gaia 控制面,包括一些管控功能,如接口生命周期管理、监控大盘、微应用管理、工单系统等等。另外如果我们有一些独立的自定义功能,会通过插件的方式集成。

feishu5

Gaia 平台功能

Gaia 平台主要包括以下功能:

  1. 业务线管理。业务线是实现以业务为维度进行接入 Admin 而提出的概念。通过业务线来聚合业务为维度的所有资源,相关资源包括微应用、菜单、接口、监控等。图中就是业务线管理的菜单页面。

feishu6

  1. 接口生命周期管理。包括接口创建、更新、编排、发布、上线、下线、删除等。同时维护接口 IDL 文件。
  2. 微应用生命周期管理。包括微应用的申请、接入、微应用版本创建、发布、下线等。
  3. 控制大盘。包括业务整体维度和单接口维度的 SLA 大盘,以及错误告警管理。
  4. 插件管理。包括默认插件和自定义插件的配置管理。

平台化实现

微前端技术架构

第三部分具体介绍飞书平台化实现,包括微前端技术架构、泛化调用实践和功能扩展。

下图是微前端的技术架构。这里涉及到三个概念,第一个是基座,即指微前端入口模块,负责组装各个模块;第二个是微应用,指独立的业务模块;第三个是微应用市场,负责管理微应用的创建,管理,版本发布等。 通过微应用市场下发的配置进行微应用组合,将基础能力下放到各个业务方。例如,现有一个新的业务线需要接入,那么它需要开发自己的微应用,打包测试并发布到我们的微应用市场,我们的基座就会从微应用市场接收到这个微应用,最后进行发布之后,就可以从 Web 看到对应模块的页面。

feishu7

泛化调用方案调研

接下来说一下后端实现的细节,即如何通过泛化调用实现整体依赖的剥离?首先讲一下这个问题的背景,Admin 的前端和后端是通过 JSON实现序列化传递的, 如果把 Admin 变成一个平台化的网关,不再维护业务逻辑,只处理通用逻辑,泛化调用是我们最好的选择。因为通过泛化调用,Admin 的网关就不需要写各种业务代码, 直接通过 RPC 接口就可以把前端传过来的 JSON 序列化数据、请求参数再传递到微服务,然后通过微服务的返回值把 JSON 序列化数据返回前端,跟前端进行交互。

我们通过调研现有框架,如网关与微服务之间使用 gRPC、Thrift 等协议进行通信,都是通过代码生成实现的协议解析和协议传输,不能动态更新,都需要生成代码,再重新发布。 而我们内部旧的 Kite 框架(Thrift 协议)不支持泛化调用,而新框架 Kitex 是字节跳动内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的特点。 在我们使用 Kitex 的泛化调用功能之前曾调研了一些泛化调用的方案,也基于 Kitex 实现了泛化调用的类似功能。但是我们认为飞书内部实现泛化调用不如推动 Kitex 的研发人员, 让他们把泛化调用变为一个通用的功能,这样不仅仅是我们团队,公司内部其余团队以及 Kitex 开源后其他外部团队都可以使用这个功能。目前 Kitex 已经支持基于 Thrift 协议的泛化调用。

非泛化调用

那么非泛化调用的实现方式和泛化调用的实现方式有什么不同呢?这张图就是非泛化调用的实现方式,无论 gRPC、Thrift 还是 Kitex 都是基于 IDL 生成协议代码, 服务端和客户端都需要依赖 IDL 生成静态代码,接口的迭代意味着服务端和客户端都需要升级代码重新发布。在 Admin 场景下意味着其他业务方的业务迭代, 需要我们引入代码依赖并发布服务,这并不符合我们平台化的需求。

feishu8

Kitex 泛化调用

在 Kitex 泛化调用中,服务端无需做任何改造。客户端只有一份通用的协议处理代码,基于已有的 IDL 信息来动态生成协议字节流,IDL 信息可以动态更新,以维护最新的接口协议,无需生成代码。 在 Admin场景下,网关作为客户端,动态维护业务方接口的 IDL,通过泛化调用来实现 HTTP 接口到 RPC 接口的转换,不再依赖业务服务客户端代码,实现了网关和业务在代码层面的解耦。

相关地址:https://github.com/cloudwego/kitex/tree/develop/pkg/generic/thrift

feishu9

HTTP 协议映射

Admin 网关是基于 Hertz 对外暴露 HTTP 协议的接口,Hertz 路由支持运行时新增,通过自定义 Middleware 和 HandlerFunc 可以实现接口运行时的增删改,这样可以实现解析修改后的 IDL 来进行接口调用。 这段代码就是初始化客户端的 Client,其实就是泛化调用的 Client,可以看到它会读业务方的 IDL,假如业务方的 IDL 有接口更新,我们可以通过这个进行业务更新,动态实现接口的上下线。 然后再构造 HTTP 类型的泛化调用 Client,每个业务方都会构造一个 Client 实例,比如有十几个业务线,就会生成十几个业务线微服务的实例。

feishu10

下面是泛化调用的路由,它其实是 HandlerFunc 的实现,通过这个方式可以动态注册路由,注册之后将 Hertz Request 转化成泛化调用 Request, 再通过前一步生成的泛化调用 Client 实现泛化调用,最后得到 HTTPResponse,再将它写回 Hertz Response中,这就是简单的泛化调用路由的实现。通过这个可以做很多业务拓展,比如错误码处理等等。

feishu11

功能扩展

具体给大家讲一下功能扩展。功能扩展的第一类就是 Kitex 提供的自定义注解,Kitex 内置了 API 注解来实现路由解析、参数传递等功能。 这里面有三个接口,第一个是 HTTPMapping,实现了参数传递、返回值等等自定义注解;第二个是 Route,实现了 Kitex 路由解析的功能; 第三个是 ValueMapping,是指将参数进行映射,比如目前 JSON 不支持 Int64,但 Go 可以支持 Int64,在使用 JSON 序列化的时候就要把参数类型定义为 String, 因此从 JSON 到 Go 就有一个转换的过程,这就可以通过映射来实现。我们通过自定义注解方式实现了框架未提供的功能,例如文件上传和下载、自定义参数注入、参数校验、自定义鉴权等。

feishu12

功能扩展的第二类就是接口编排,已经实现的单接口泛化调用,不能完全满足我们一些复杂场景的使用需求,例如:简单组装两个接口的结果,比如同时调用接口 A 和 B,再将两个接口进行组装; 接口有顺序依赖,一个接口结果是其他接口的参数,比如先调用 A,A 的返回值作为参数去调用 B,再将 B 的返回值作为整体接口的返回值。 Kitex 和 Hertz 还不能支持接口编排的功能,所以我们通过自定义 DSL 引擎来对简单接口进行编排,以便实现一些复杂场景的接口调用需求。

feishu13

成果

最后给大家介绍一下我们的演进成果,主要有以下三点:

  1. 业务迭代加速。Admin 不再关注其他业务线的需求,更加专注于自身的迭代需求。各个业务方发布完全隔离,使得他们不再依赖 Admin,加快了 Admin 整体的业务功能迭代速度。
  2. 研发效率提升。丰富的前后端组件和简单的接入方式,业务方不需要再花费时间熟悉我们的代码仓库,使得业务方接入更加便捷,研发效率大大提升。
  3. 工程质量提高
    • 其他团队不再向 Admin 仓库提交代码,仓库代码风格趋向统一;
    • 去除了大量的业务逻辑,聚焦网关通用逻辑,提高了单测覆盖率;
    • Bug 率显著下降,服务 SLA 明显提升。

未来规划

我们目前制定了一些未来的发展规划,主要有以下四点:

  1. 开放更多的组件,让接入的业务方聚焦在业务逻辑本身,例如组织管理里面的选人组件,之前需要各个业务方自己内部实现,之后我们会提供一套公共组件,业务方可以直接使用,包括消息中心、任务管理、安全风控、短信邮件等;
  2. 完善服务治理和运维能力,包括灰度、降级、限流、精细化大盘等;
  3. 建设通用的静态页面托管解决方案,为开发者提供便捷、稳定、高扩展性的静态页面托管服务;
  4. 对接集成测试平台,闭环路由管理生命周期,保障接口稳定性和安全性。

最后修改 August 2, 2023 : docs: add document http3 (#737) (5cdb2f3)