4

知乎效果营销平台前端工程治理

 2 years ago
source link: https://zhuanlan.zhihu.com/p/443188802
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1、背景

知乎效果营销平台旨在帮助企业和个人进行广告的投放。集注册开户、平台充值、平台使用、营销学习等服务于一体,专注于构建知乎效果营销一站式服务。伴随着业务的快速发展,各个业务代码全部包含在单库里,技术层面项目的可维护性、可扩展性变得极差,我们深切地感受到 2 个痛点:

1.1 开发环境复杂

  1. 开发环境启动困难:项目启动方式是人传人,依赖 charles,配置也五花八门,组里同学无法在不需要协助的情况下成功启动开发环境。
  2. 数据 mock 体系没有,依赖 charles 代理到目标环境。

1.2 单仓库多应用

  1. 工程代码量巨大:所有的业务代码都放到单仓库里,业务代码量已达到 14 万行,新接手的研发同学难理解且负担重。
  2. 部署不合理:仓库并没有采用 monorepo 或者 multirepo 方式组织业务代码,而是以目录命名去划分业务边界,用同一个 webpack 构建脚本,一旦有迭代部署,所有的业务平台全量重新构建部署。
  3. 跨业务直接调用组件,交集混乱:存在很多业务之间项目调用资源的情况,比如「广告主」直接引用调用「运营端」目录下的 UI 组件。

业务的快速迭代,功能的持续增加,如果没有切实有效的技术手段,工程将变得越发臃肿。业务层面的这些痛点,也在不断地督促我们去反思。经过一些实践,我们积累了一些经验和结论,希望相关的经验和结论能够帮助到更多的个人或技术团队。

2、工程治理思考

2.1 开发环境治理

我们需要一键启动项目,达到 「Less Config」与 「Less Dependency」状态。

  1. Less Config: 尽可能让研发同学不需要做一些前置配置工作。
  2. Less Dependency:研发同学 clone 完仓库直接就能 yarn install, yarn start 开发,不再依赖 charles 等工具。

2.2 单仓库多应用拆分

仓库不管最后以「monorepo」或者「multirepo」进行拆分,我们最终的诉求都是业务独立开发、独立构建部署、应用之间可共享模块、业务相互之间不影响,通过技术方式将应用组合起来。很明显,这需要微前端的方式来治理。

3、开发环境治理实践

3.1 一键启动开发环境

该项目采用 webpack 构建,所以我们可以放弃用 charles 代理,采用 webpack-dev-server 配套设施来代理。同时要代理登录/登出 cookie 问题。

生产环境是 https 协议,我们本地启动开发也需配置 https,采用开源工具 mkcert 来签发本地 https 证书。

来看一下开发环境自动化流程设计:

整体核心流程可分为

  1. 启动项目时搜集启动进程参数。
  2. 自动签发 https。
  3. webpack-dev-server 代理到目标环境。

项目启动会搜集进程启动参数,用于确定开发环境时候要代理到哪个目标环境,以及对应的域名,这一部分采用控制台交互工具 inquirer 来完成。

签发本地 https 证书采用的开源工具 mkcert,在自动化的初版中,采用 brew 去安装,脚本会自动检测研发电脑里是否安装 brew,如果有就执行 `brew install mkcert` 去安装 mkcert。

但该方式有个问题,brew install 偶尔会报错,如 brew 更新更或者一些其他的错误,总体来说,并不是太稳定。于是采用从 gitee 下载 mkcert 的二进制可执行文件,同时我们还留了一个口,当 gitee 服务出问题,我们可采用从 github 上下载的方式。

下一步就是生成 https 证书,并且只生成一次,如何保证证书只生成一次?当证书第一次生成成功后,在本地写一个隐藏文件,下次生成的前先判断这个隐藏文件是否存在,来避免重复生成。

有了证书,我们还需要配置 webpack-dev-server,来确保本地 server 能读到证书。

接下来要解决代理问题,代理我们放弃了 charles,主要是因为:

  1. 我们是 PC 端,chrome devtools 已经满足接口抓包分析的诉求。
  2. charles 比较吃内存,长时间开着系统会越来越卡。
  3. 配置麻烦,并不是每个前端都擅长配置 charles。
  4. 前置依赖,开发环境应该保持极简,在可能的情况下依赖越少越好。

由于我们是 webpack 作为构建工具,自然而然使用 webpack-dev-server 来处理代理服务。业务接口代理比较简单,重点登录/登出代理,这里关键是需要去分析 sso 服务的登录/登出流程。

登出:sso 服务登出是一个特定的接口 /logout。当登出时,接口清除 cookie,并重定向到 sso 登录页面。
登录:sso 服务登录成功后,会访问项目 /callback 接口,取重定向地址跳转到系统页面。

分析完后,我们只需要拦截对应的接口,做一些拦截操作,就能成功串联起本地服务与 sso 的串联。

这里会有疑问:登录代理如何保证目标 cookie 在本地环境有效。我们的 cookie 权限是支持子域访问的,这里就采用了一个比较巧妙的域名设计,本地开发环境域名采用线上环境的子域名:

这样就解决了不同域 cookie 权限问题。

3.2 代理目标环境切换

代理的目标环境有「生产环境」、「测试环境」、「联调环境」,生产环境与测试环境域名是固定的,我们可以通过 select 交互式命令行自由选择。而联调环境的域名是研发同学自己动态生成的,那该如何代理?

这里我们设计了一个配置文件,在项目根目录下建一个 .env 文件,并配置联调环境地址。

3.3 修复 MOCK 系统

效果平台早期的前端开发环境是有 mock 系统的,把 mock 文件放到 __mock__ 目录下,开发环境直接去读 mock 文件。而到现在 mock 系统是不可用的,究其原因:

  1. mock 登录不方便。
  2. 最初某几个 mock 文件缺失或者 mock 数据不同步,导致开发服务不可用,也没人去修复,直接使用对应环境的代理,这是一个蝴蝶效应,年久造成 mock 数据差异,文件缺失。

针对登录权限,登录是稳定的,属于外挂服务,这里我们直接采用代理,不用 mock,直接走对应环境的 SSO 服务。

针对业务接口,现在已经无法溯源接口文档,自然我们也无法一个一个去补充 mock 文件,那么问题核心就是:只要本地有正确有效的 mock 数据,我们就可以使用 mock 系统。所以我们以生产环境数据为基准,同步生产环境接口数据到本地的 mock 文件。

这里借助 webpack-dev-server 代理层为入口,劫持到代理数据写入到 __mock__ 目录对应的文件里。如下伪代码。

枚举出所有的业务接口同步到本地后,就可以在开发环境中使用 mock 系统了。

以上就是效果营销平台前端开发环境治理过程,总体上来讲,我们完成了以下目标。

  1. 开发环境一键启动,无需要配置,也没有 charles,工程 clone 下来依赖安装后,直接就能启动,不再需要人传人的方式协助启动。
  2. 本地开发环境也支持 https,与线上环境保持一致。

4、单仓库多应用拆分 - 微前端

4.1 到底什么是微前端?

我们的面临的业务主要是中后台系统,有些系统迭代时间长,代码量庞大,系统与系统之间有重复的 UI 等,造成了开发协作困难,部署慢,全量部署等问题。市面上微前端框架主要以 single-spa.js 或者基于 single-spa 的 qiankun.js,而这些框架都提供了项目组织能力。

微前端是并非某个技术的本身,而是一种大型项目组织的方式。而市面上的微前端框架的实现,也是组织方式实现的。

那么组织具体指什么?

  1. 应用的边界。
  2. 应用之间共享模块。

组织应用边界,组织应用之间共享模块,很容易联想到如下架构,一个基座 app, 以及若干子 app。基座 app 提供一种「加载子 app」或者「提供公共」的能力。

业界用到此组织方式的代表有 「qiankun.js」 与 「小程序分包」。

一切仿佛都很美好,但如何组织子应用之间模块共享,是个难题。如何组织模块共享,常见的有 3 种。

  1. script.src 引用
  2. 公共模块放主应用里,子应用引用主应用的公共模块。

这几种方式都一定程度的解决公共依赖,但有一个问题:成本。

假设我们定义至少 2 个子应用都用到模块算作是公共模块。

如果采用 script.src 方式,也需要将各模块打包成 umd 模块格式,放到 CDN 上,相当于各模块需要维护一个独立的打包,部署体系。

如果用 npm 方式,诸如项目的 utils 等。那将会产生多个 npm 包,不仅会重复打包相同模块,还会衍生另一个问题:维护众多的 npm 包的成本。

如果都放到主包里,有些已有的公共模块,将来可能定义为公共的模块都会统统放到主包里,主包会越来越膨胀,以及会产生一个维护职责划分问题,谁去维护对应的公共模块?有过小程序开发经历的同学可能会身感同受这个场景。

微前端的概念来自于微服务,微前端与微服务最大的区别就是运行环境,每个微服务可以运行在一个实例容器里,如云虚拟机或者 Docker 里等,而微前端的运行环境就一个实例,比如浏览器等。

但微服务有个重要的特性是:每个微服务都可以一个服务生产者,也可以是一个服务消费者。服务生产者需要给服务消费者提供特定的接口去消费。

那么如果我们把在微前端的实践也往微服务上面靠:如果是一个公共模块,它暴露出特定的接口出来,供那些消费者使用,当使用的时候就远程加载特定的模块 bundle 代码。

从上面图里可看出,不同应用的公共模块都可以作为生产者,也可以作为消费者,作为生产者只需要提供特定的接口,不管它放到什么项目里,什么样的目录里。

4.2 webpack 联邦模块 Module Federation

Module Federation 是 webpack5 令人兴奋的特性之一,并没有主/子 app 概念,只有生产者和消费者概念,真正解决了应用之间模块共享,直接看代码。

如上:App1 在 3001 端口,App2 在 3002 端口,App1 作为生产者暴露出特定的模块接口 Button,App2 作为消费去使用 Button。每个暴露的模块都是一个特定的 bundle。

当加载 App2 时候,App2 会加载消费的模块入口文件, remoteEntry.js 以及消费的模块 src_Button_jsx.js。

以上就是 webpack5 Module Federation 处理模块共享问题的方案。

4.3 效果平台如何做微前端治理 (方案)

回到我们真实案例,我们效果平台有多个业务,如广告主、运营平台等,各个子业务代码都是以特定的目录名目录划分的,公共模块放到特定目录。

项目拆分:

  1. 业务代码以及放公共代码的目录,都录独立成一个仓库,业务代码的仓库 src 目录下必须有一个 entry.js
  2. 所有仓库都是用同一套构建脚本,启动多个构建服务。

模块共享:

基于 webpack5 module federation 的模块共享方式,拆分的应用只需要关注 2 个问题:

  1. 当前项目叫什么名字。
  2. 当前项目暴露哪些模块接口。

开发同学只需要关注当前项目暴露什么模块的 API 就行,其他诸如配置里的 remotes 等配置我们可以隐藏起来。此时设计一个工具:zmicro.js。

于项目研发同学:在项目根目录写一个配置文件:zmicro.config.js

工具会搜集每个项目的配置文件,实例化到 webpack 联邦模块插件里,如下伪代码思路:

之所以这样设计,我们期望解决微前端的是一个轻量级的插件,只需要使用一些简单的配置,并不需要过多的代码入侵,也不会跟某个集成编译环境的 CLI 绑定,它就是一个插件,适配所有基于 webpack 的项目。

4.4 治理效果

开发方式:多个独立项目,都放在独立的不同仓库,启动不同端口的开发服务。如下图,效果平台启动入口工程与广告主工程。

跨项目公用组件/页面:如引用广告主 Ad 组件/页面

运行时效果:

如图所示,在 5718 端口上的应用(消费者)去加载 3333 端口应用(生产者)资源渲染出页面。

5、总结与展望

工程治理(环境治理/微前端)不仅可以解决复杂的业务系统,而且还是比较热门的技术方向。随着前端工程的日益复杂化,对可扩展的前端架构的诉求也变得更加强烈。微前端作为一种前端解耦的方案,自然更加频繁地被大家所提及和应用。

目前市面上常用的微前端方案,最常见的方案有:

  1. iframe 方案。
  2. 主/子应用方案,代表有: qiankun.js 、小程序分包。
  3. Webpack5 Module Federation 方案,代表有: emp.js。

总结:

  1. iframe 方案有解决不了的用户体验问题。
  2. 主/子应用方案,是基于项目拆分维度的方案,各项目之间关系隔离,很难从根本解决跨应用之间模块共享问题。
  3. 业界 emp.js 基于 Webpack5 Module Federation 方案,解决了不同应用模块共享问题,但 emp.js 集成了自己的开发编译环境。

我们的方案,采用基于 Webpack5 Module Federation 的轻量级插件 zmicro.js,不绑定任何开发流的编译环境,适配所有基于 webpack 的项目。

经过长期的技术和业务演进,目前营销平台前端治理能够有效地帮助研发人员大幅提升开发效率。

  • 一键启动:开发环境自动化,达到 「Less Config」与 「Less Dependency」状态,不需要做一些前置配置工作,clone 完仓库直接就能开发,不再需要去依赖 charles 等工具。
  • 业务独立:业务独立开发、独立构建部署、业务相互之间不影响。
  • 扩展能力:让一键启动流,微前端接入更多的业务系统。

但是仍有许多业务场景值得去我们探索。


杨涛、孙威、李学鹏均来自知乎商业广告事业部。

知乎商业广告事业部诚招高级、资深 FE/ios/Android,Base 北京。我们致力于建设最具想象力、创造力和影响力的大前端团队。我们是一群热爱技术的小伙伴,每个人都有自己擅长的领域,靠谱,自我驱动。

欢迎有兴趣的同学投送简历到:[email protected][email protected]

高级/资深前端

职位职责:

  1. 根据产品和运营需求,多方位合作,完成知乎商业前端研发工作(H5、Web 、Hybrid 、SDK)。
  2. 参与前端技术方案的整理和实践,系统架构的设计和优化。

任职要求:

  1. 5 年及以上前端开发工作经验。
  2. 扎实的前端基础 JavaScript/HTML/CSS,熟悉页面架构和布局,熟练使用 React、Ant-design pro 进行前端开发。
  3. 对计算机相关基础知识有较好理解,了解数据结构和算法设计。
  4. 对 iOS 和 Android 开发有了解,理解 Native 与 Web 如何通信。
  5. 有良好的沟通能力,能够顺畅的和他人合作。

加分项:

  1. 对 React、Hybrid、Server-Side Rendering、可用性及性能优化等方向有深入理解。
  2. 有 Nodejs 研发经验。
  3. 有 iOS 和 Android 研发经验。
  4. gitHub上有独立作品。

iOS

职位职责:

  1. 参与 iOS App 商业产品的技术方案规划、架构设计和系统设计。
  2. 负责移动客户端 (iOS) 商业产品的基础组件、通用模块的设计与开发。
  3. 根据产品和设计师的需求,能独立或者主导进行 GUI 交互的设计与开发。
  4. 研究并实践移动客户端领域的新技术,赋能业务、提升效率。

任职要求:

  1. 五年以上 iOS 相关开发经验。
  2. 具有扎实的编程功底,良好的数据结构与算法基础、网络基础,良好的设计能力和编程习惯。
  3. 熟练使用 Git、CocoaPods、Instruments 等 iOS 开发常用工具。
  4. 深入掌握 Objective-C 或 Swift 语言、Cocoa Frameworks 和 MVC 设计模式,对 OCRuntime 有深入理解。
  5. 精通 Mac 或 iOS 下的并行开发,网络开发,内存管理等。
  6. 对界面美观度和用户体验有较强的敏感度。
  7. 良好的团队协作能力和沟通能力,有责任感,对移动端产品有浓厚的兴趣。
  8. 有强烈的求知欲和进取心,对新技术有持续的热情加分项。

加分项:

  1. 参与设计开发商业 SDK 者优先。
  2. 有动态化开发经验者优先。
  3. 有 MVVM、响应式编程、函数式编程、ComponentKit 开发经验者优先。
  4. 有 Flutter 开发经验者优先。
  5. 有大型成熟项目的架构设计经验者优先。
  6. 对知名的 iOS 开源项目有过代码贡献优先。

Android

工作职责

  1. 完成商业广告相关业务需求,负责商业广告业务模块的技术选型、架构设计、开发和优化。
  2. 指导团队其他成员的技术设计和 Code Review,保证工程质量。
  3. 优化 Android 小组的开发方式和流程、规范,提高团队效率。

任职资格

  1. 本科以上学历,5 年以上工作经验。
  2. 精通 Java,熟悉 Kotlin,具备扎实的计算机基础和良好的编程习惯。
  3. 丰富的 Android 知识及应用能力,对 Android 系统架构和运行机制有深入理解。
  4. 具有良好的工程能力,能够独立解决有一定复杂度的问题。
  5. 丰富的开发、架构设计经验,能快速搭建、调试或重构大规模软件代码。
  6. 自驱力、学习能力强,执行力好,良好的沟通协作能力,注重效率,能够深刻影响其他人。

加分项

  1. 有自己的技术博客或者开源项目作品。
  2. 参与过知名开源项目,提交过 issue 和 patch。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK