phodal

phodal 查看完整档案

西安编辑西安文理学院  |  电子信息工程 编辑Growth Studio  |  软件开发工程师 编辑 www.phodal.com 编辑
编辑

待我代码编成,娶你为妻可好 @花仲马

个人动态

phodal 发布了文章 · 1月22日

2020 结点:平凡 & 重新出发

2020 年,庚子年,注定是不平凡的一年,所以就平凡的过去了。年初,疫情让我在家办公了几个月,年中开始了忙碌的几个月,年底又归于平凡。也因为疫情,多了一些 beach 的时间,不得不休完 20 天的看似,还有没机会用上的婚假,所以我有机会尝试一些新的想法。

太长不读版:

  • 编程上,回到底层/系统编程,构建基础设施开发能力。
  • 写作上,在 Ledge 项目上结合前端可视化,展示了知识管理的另一种可能性。
  • 设计上,依旧还在一天一张画的练习上,暂时没有新的突破。
  • 方法化上,丰富和完善了 DevOps/系统重构相关等知识体系。
  • 影响力上,靠影响力带来了几个公司的项目,除此没有进展。

好像也没了。再对比一下上一年的目标:

  1. 工具,有了更多编程语言、软件工程相关的工具。
  2. DSL 抽象,设计的 DSL 主要集中在 Charj 相关的项目上,缺少对业务的抽象。
  3. 国际化,几乎没有任务长进。相反的,在做本土化的各种实施。
  4. 婚礼,被迫放在 2021 年了。

嗯,大部分都没有实现,反正计划就是计划嘛 :) 。

编程

综合疫情带来的 beach 时间,加上外地出差,额外获得了很多的编码时间。

项目相关

这一年的项目多少是有些无聊,设计一些方案,指导实施方案的落地,再做一些度量。

参与了某国产操作系统的 IDE,深入了解 Android Studio 和 Intellij Community 背后相关的知识、各类实践。真正意义上,掌握了编程语言端到端的实践 —— 从开发、构建、优化,再到执行等一系列的过程。源码阅读上,包含但是不限于 Gradle、Proguard、R8/D8、JVM、Intellij Community 等。

底层编程 + Rust

在那了篇《六年之后:回到底层编程》里,我开始了底层编程之旅。

  1. Electron + Rust 设计 RPC 架构下的客户端:Stadal
  2. 可执行的 markdown 工具 exemd (支持依赖):exemd
  3. Scie 代码识别引擎:Scie
  4. ……

不过,就目前的情况来看,道路依旧还有点长,需要重新掌握的知识有很多 —— 毕竟以前看会的,和现在真正动手的是两码事。

重构工具

在这一年里,与工作相关的一部分话题依然是重构。所以,也利用了大量的业余时间。

  • 更完善的分析工具:Coca
  • 多语言分析工具:Chapi
  • Ant 转 Maven 工具:Merry
  • 和同事搞的 CSS 重构工具:Lemonj

有意思的是,这几个项目的技术栈是:Go + Antlr、Kolint + Antlr、Go + Antlr、TypeScript + Antlr ……。嗯 ,真的是只要涉及编程语言相关、DSL 相关,Antlr 就是一个非常不错的工具。

DSL 与 Charj

快到年底的时候,和我同事一起开启了 Charj 语言的坑,也是为自己的未来找一些有意思的事情干。我们日常做项目的时候,最难的就是启动一个项目 —— 要搭建架子,相当于设计架构。所以,在这一年里,努力地把整个架子搭建了起来。

这一个也作为了下一年,或者是未来几年的的一个方向。(PS:有兴趣的话,欢迎入坑,微信:phodal02 (注明来意))

写作

写作最重要的是,构建成了一个完整的体系。虽然我平时写的文章多,看上去没有体系,但是还是有一些基本的体系的 —— 也就是围绕着我要去做的东西。

万物代码化

关于这部分内容的总体思路:《万物代码化:从低代码、云开发到云研发》,这部分的各部分文章见:

完整内容见:https://github.com/phodal/asc...

知识体系构建

工作时间越长,越发现知识体系的重要性。哪怕是写了一系列的文章,查阅的时候,也算是过于分散了。在这一年里,主要梳理了这两部分的知识体系:

  • 《遗留系统重构指南》:https://github.com/phodal/mig... 。 手把手教你分析、评估现有系统、制定重构策略、探索可行重构方案、搭建测试防护网、进行系统架构重构、服务架构重构、模块重构、代码重构、数据库重构、重构后的架构守护。我原以为这是一个很小众的领域,没想到年底的时候一看,GitHub 上有 2k 的 star。
  • DevOps 知识体系:https://github.com/phodal/ledge 。基于在 ThoughtWorks 进行的一系列 DevOps 实践、敏捷实践、软件开发与测试、精益实践提炼出来的知识体系。它包含了各种最佳实践、操作手册、原则与模式、度量、工具,用于帮助您的企业在数字化时代更好地前进,还有 DevOps 转型。 反而是我看好的这个项目,GitHub 上的 star 只有 1.3k 。

接下来要做的事情就是,在适当的时候构建下一个知识体系。

其它

其它多数为一些总结,可以在未来用到。又或者是诸如『编程语言开发』这一个还不成统的话题。

设计

没有特别突出,依旧是画画。

不过,画得似乎越来越普通了?

唯一发生的变化是,我换了新的产生力(爱-奇-艺)工具:iPad Pro 11 + Apple Pencil 2。

其它

我一直有一个想法是:建设一个开源梯队。不过按国内的加班情况来看,这种可能性并不是很大。只能试着围绕 Charj 来构建开源社区了。

Helo, 2021

简单,然后专注,这就够了。

本文参与了 SegmentFault 思否征文「2020 总结」,欢迎正在阅读的你也加入。
查看原文

赞 2 收藏 0 评论 0

phodal 发布了文章 · 2020-11-23

Charj —— 代码的代码化语言

去年,和公司的大佬讨论了一系列关于代码的代码化,还记录了一些笔记。在那之后,我开始了各种尝试:如何将代码转变化代码。原先有一些思路,而后过了一年之后,慢慢地练习,又有了一些新的收获。

我们想要做的事情是:把任意的 A 语言转换为任意的 B 语言(PS:这里的任意 A 和任意 B 语言都是主流语言)。如此一来,我们便可以:

  1. 快速重写任何的系统。
  2. 与编程语言无关的领域建模。
  3. 产生一个更强大的 DSL。
  4. 创建新的语言。

引子 0:统一语言模型

统一语言模型,即对不同的比编程语言进行抽象,使用同一套数据结构描述编程语言。

在我使用了 Golang + Antlr 实现了 Coca 之后,我意识到这是一条可行的方案。但是,由于 Coca 的架构和用途所限,外加之 Antlr 对于 Java 的支持远比 Go 要好,我并没有继续在 Coca 上实施这个方案。

于是乎,我开始了第二个尝试,使用 Kotlin + Antlr 来实现对不同语言的模型统一,也就是我的另外一个开源项目 Chapi。但是呢,随着不断的尝试,我发现了其中的难度和工作量比较大:

  1. 编写不同语言的语法解析。社区上已经有大量的成熟的轮子,其中最出名的就是 Antlr 相关的语法解析。官方维护的代码仓库(grammars-v4)包含了大量的 Antlr 语法解析案例,可以找到市面上一些主流的和非主流的实现。
  2. 设计统一语言模型。即设计出一套能兼容不同语言的语言模式。当然了,这是一个持续完善的过程,会随着更多语言的加入,变得更加完整和复杂。
  3. 解析不同语言。即根据不同语言的语法特性,转换为上述的模型。

从难度上来说,我们可以看出技术难度主要是在步骤 1 和步骤 2。而步骤 3 呢,则是一个非常繁琐、工作量巨大的体力活。我们还需要熟悉不同的编程语言,并一一解析对应的字段,才能转换每一个语言。

因此,我尝试建立起了 Chapi 的社区,然后手把手带领一群人干活。尽管,对于不同的语言我已经建立起了统一的编写模式:TDD + Tasking。似乎,很多人对于 AST 有点担心,因此参与的人非常少。所以,对于其它语言的支持就不了了之。

相关资源:

引子 1:语法高亮的背后

与此同时,哪怕有足够的人,Antlr 并非一个完美的答案。在编写不同语言的支持时,我依旧遇到一系列的 Antlr 语法不支持的问题。如 JavaScript 的 Import,Java 的一些 Lambda 问题……。换句话来说,Antlr 官方只是维护这么一个库,真实的效果就不得而知了。

于是,我就回到了一条老路上,使用正则——当然不会自己写了。在那篇《编程语言的 IDE 支持》中,我提到了基于正则表达式来实现语法分析,其中介绍了两个编辑器的实现方式:

所以,我们选择了 VSCode 作为了语法解析背后的语言。在这种模式之下:

  1. 我们有一个成熟稳定的语言解析工具,并且也有一个巨大的团队在维护它们。
  2. 它的社区是非常庞大的,经过大量的反复提升。

因此,我和我的同事从几个前开始编写:https://github.com/phodal/scie/ —— 一个基于 TextMate 语法高亮的库。

引子 2:代码生成与 JavaPoet

在我们粗糙地完成了 Scie 之后,我开始思考着下一步:如何从 A 语言转换为 B 语言的时候,我从 JavaPoet 获取到了一些灵感。JavaPoet 是一个用来生成 .java 源文件的 Java API。如下是一个简单的 JavaPoet 代码示例:

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

也就是说,我们可以写一个 API,以将某语言转换为 B 语言的源码。而要实现任意语言的转换,那么我们就需要实现一个 DSL:用于描述不同语言与统一模型的差异。后来,我意识到我还需要另外一个 DSL,用于转换统一模型到不同的语言

引子 3:中间表示的演变

编译器的核心数据结构是被编译程序的中间形式。 —— 《编译器设计》

理论上,通过上述的两种方式,我们就可以直接生成不同领域的模型。但是呢,为了调试方便,可以创建一个中间语言来作为它们的承载物,可以让我们实现更有意思的事情,去统一进行编译器优化——当然,我是瞎说的。

随后项目的原因,我研究了一小段时间的 Proguard + D8 和 Android R8 的实现上。它们两做的事情是相似的,将 .class 字节码,编译优化,再转换成 Android 手机上的 dex。当然了,转换为 Aot 就是一个更有意思的话题了(虽然我也不熟悉)。但是呢,这期间涉及到了一系列的中间状态:java -> .class -> .dex -> odex -> .oat。即从 Java 代码到 JVM 虚拟机字节码 -> Dalvik 虚拟机字节码 -> 优化过后的 Dalvik 字节码 -> ART 机器码。

而我们再回过到来看,编码语言本身也是一种中间表示,因为机器运行的是靠机器码。即,那句经典的话:代码是写给人看的

引子 4:DSL 的 DSL

对于有的编译器来说 ,它们可能有唯一的 IR(中间表示,Intermediate representation),也可能会有一系列的 IR。最常见的一些实现,便是我们看到的那些使用 LLVM 作为后端的语言,它们可以生成中间形式的 LLVM IR。同样的对于我们想做到的事来说,我们可以设计一个类似于 LLVM IR 的高级中间表示,用于承载语言的设计。

由于项目涉及到一丁点的代码优化,所以我还阅读了一下那本《高级编译器设计与实现》,书中引入了 ICAN 这个中间语言。嗯,这就是已经被论证的结果了,不再需要我去论证它的必要性。所以下一步就是:

自举,在计算机科学中,它是一种用于生成自编译编译器的技术,即使用打算编译的源编程语言编写的编译器。

在业内,人们往往往把自举定义在编译器领域中。但是呢,它可以在更多的领域被应用。例如 Java 的构建工具,Gradle 使用 Gradle 来构建自己 —— 当然与编程语言相比,这事要相对容易一些。

而人的自举就是把自己替换便,让工具做了自己的事,让别人做得了自己的事。所以,我们就需要 Charj 来做自己所能做的事情。

Charj Lang

终于回到了正题上了,在有了上面的几步之后,我们就能:

  1. 通过正则表达式,解析、生成不同语言的语法树。
  2. 编写 Poet API 将上述的语法树,转换为某一特定语言源码。
  3. 设计某一中间语言,用来作为 A 语言转换为 C 语言的载体。
  4. 实现 A 语言到 C 语言,又或者 C 语言到 A 语言的自由转换。

这便是从任意语言转换为任意语言的想法和思路。于是乎,我和我的同事们开始设计一个中间语言:Charj。

当然了,开发一个语言的目的主要是为了锻炼自己的能力,不论是抽象能力,还是算法能力等等。在这个漫长的人生里, 它将会变得有意义。以后,请叫我 Charj 语言作者。PS:你也可以是 Charj 语言作者。

回过头来看,事实上应该是这样的,我已经尝试造了各式各样的工具,从各类的编辑器到各类的命令行工具。而在学习了 Rust 之后,我研究了 JVM、编辑器底层,也正在逐一尝试创建日常所使用的工具。而在上一年里,因为编写重构工具 Coca,再到随后的转换为统一语言模型的 Chapi。对于编译器前端,我已经有了相当丰富的经验。自然而然的,创造一个语言就成了下一个方向。

为什么叫 Charj ?

从本义上来说,Char 是更适合 Charj 的定义的,但是 Char(仓颉)的商标已经被注册了。退而求其次,我只好叫 Charj,可以引伸为中英混合式的:字符(Char)集(Ji),又或者是字符(Char)集(姬)。又或者是『字符 J』 —— 至于 J 是什么意思,我还没想清楚。我们可以再定义,再取一个新的名字。

Charj 进展

Charj 使用的是 Rust 为主的语言编写的。Rust 的自举已经证明了:Rust 用于开发编程语言是没有问题的。当然了,主要原因还在于让我 C++,还不如让我写 Haskell。

Charj Lang (设计中)

Charj lang 现在的工作分为两部分:

  1. 完善语法设计
  2. 编译器的流程设计

尽管从理论上来说,Charj 不一定需要编译 + 可运行,但是为了自举,我们需要它们。于是,我们在后端采用了 LLVM,前端使用的是 Rust 里的 LR(1)解析器生成器 lalrpop

GitHub:https://github.com/charj-lang/charj

Charj IDE(开发中)

当前已经有一个简单的语言插件,当然只有基本的高亮和跳转功能。如果你有一定的 IDEA 插件开发经验,也可以来我们一起搞搞。

GitHub:https://github.com/charj-lang/intellij-charj

Scie

Scie(Simple Code Identify Engine)是一个基于正则表达式的通用语言转换器。主要开发工作基本已经完成了,但是有几个问题需要解决:

  1. 效率优化
  2. 调用 Oniguruma FFI 时会随机出错。

GitHub:https://github.com/charj-lang/scie

Charj Poet(开发中)

Charj Poet 是一个是用于生成 Charj 代码的 Rust API。计划等语法设计完,再进一步完善。

GitHub:https://github.com/charj-lang/charj-poet

Poet DSL(待定)

两部分:

  1. 即设计一个新的 DSL,来描述不同语言转换为 Charj Lang 的 DSL。
  2. 即设计一个新的 DSL,来描述 Charj Lang 转换为不同语言的 DSL。

官网

简陋和粗糙的官网:https://charj-lang.org/

其它

此时此刻,虽然我翻过几本编译相关的书籍,我也并非一个编译原理相关的专家。所以,如果你也有兴趣,欢迎来加入我们。

查看原文

赞 3 收藏 1 评论 1

phodal 收藏了文章 · 2020-05-02

前端架构之 JAMStack

什么是 JAMStack

JAMStack(JAM 代表JavaScript,API 和Markup)是一种使用Static Site Generators(SSG) 技术、不依赖 Web Server 的前端架构。

它的核心是:不依赖 Web Server。

这看起来和把一个静态网站部署到文件服务器没什么区别。我理解,JAMStack = 现代 SSG 框架 + DevOps + Serverless,是一种「究极」的前后端分离。

激进的说,「Static is the new dynamic」。

cdn

一种有趣的比喻是:「CDN 优先应用程序」。

JAMStack 好在哪

  1. 高性能:由于网页是静态生成的,没有额外的网络数据请求,它的Time to first byte(TTFB)性能是最佳的(因为不涉及后端、数据库等等)。
  2. 易部署:因为 JAMStack 不依赖 Web Server,部署就仅仅是把生成的网页放到CDN就可以了。
  3. 强安全:同样因为不依赖 Web Server 的原因,就导致 JAMStack 网站的攻击面很小。
  4. 易开发:JAMStack 由于其特性,开发也极其简单,不强依赖后端,开发、测试仅仅是部署到一个静态文件服务器即可。现在「三大框架」都有相应的 SSG 方案,学习成本不高。

对比 Client Side Rendering(CSR),SSG 的 TTFB 有明显的优势;同时由于提前渲染,SEO 也更友好。

对比 Server Side Rendering(SSR),SSG 部署简单,直接放到 CDN 即可,不依赖 Node Server 动态渲染。TTFB 也优于 SSR。


Next.js的作者之一Guillermo Rauch提到,由于 JAMStack 的易部署特性,给整个前端的开发测试流程带了翻天覆地的变化:「The Deploy URL, the Center of Gravity」,即在开发、测试、验收等等的流程中,核心是围绕 URL。

  • UX 想看到现在开发的网页效果,开发只需要部署到一个暂时的 URL,然后把它发给 UX 就可以了;
  • 多个 feature 分支,测试都可以在自己独立的 URL 中实时看到效果;
  • E2E 测试、用户测试也独立在一个 URL 中;
  • 等等...

url2

如何实现 JAMStack

现在「三大框架」都有相应的 SSG 方案,既满足了多样化、复杂化的前端开发需要,又能简单的生成静态网页:

  1. Next.js是基于 React 的 SSR/SSG 框架。
  2. Scully是基于 Angular 的 SSG 框架。
  3. VitePress是 Vue 官方推出的 SSG 框架。

部署方面有VervelNetlify腾讯云云开发等等。

Ledge项目的实践

Ledge(源自 know-ledge,意指承载物)知识平台是基于我们所进行的一系列 DevOps 实践、敏捷实践、精益实践提炼出来的知识体系。

Ledge 网站使用 Angular 开发。作为一个类 Wiki 型的网站,它使用 Markdown 作为编写内容的语言,使用Ledge Framework动态的将 Markdown 转换为 HTML。这就涉及到,如果转换过程在浏览器进行,势必导致性能的下降,和对 SEO 的影响。

所以我们选择了Scully作为 SSG 框架,在 build 阶段依据路由将内容提前转换为 HTML 页面:

ledge
项目的开发部署流程

各方面性能都有了大幅提高:

data
对比 JAMStack 和 CSR

查看原文

phodal 发布了文章 · 2020-04-18

文档代码化

文档代码化,将文档以类代码的领域特定语言的方式编写,并借鉴软件开发的方式(如源码管理、部署)进行管理。它可以借助于特定的工具进行编辑、预览、查看,又或者是通过专属的系统部署到服务器上。面向非技术人员的文档代码化的一种常见架构模式是:编辑-发布-开发分离』,

最近一个月里,我在开发一个基于 Git + Markdown 的全新文档系统。我定制了一个基于 markdown 的标记语言,以支持起雷达图、条形统计图、思维导图等图表的文档系统。这个系统将在未来几个月内发布。当然了,视进度而看,也可能是月底。

过去的几年里,我们一直在讨论各种各样的代码化,基础设施代码化、设计代码化、需求代码化……。在我的那一篇《云研发:研发即代码》中,设计了一个完全代码化的软件开发流程。而今天我们将讨论另外一个有趣的存在:文档。

在《架构金字塔》中,我将文档定义为支撑五层架构模型的一种存在。因为文档在一个系统中是非常重要的存在,我们用它来指导开发工作,用它来记录问题,用它来写下规范……。总而言之,它很重要,所以我们重新讨论一下这个话题。

引子 1:架构决策记录:格式化文档

三年前,当我第一次接触到『架构决策记录』的概念时,我被它的理念所吸引:

  • 使用轻量级文本格式化语言描述重大决策
  • 跟随代码一起版本化
  • 使用某种特定的文档格式(标题、上下文、决策、状态、后果)

随后,我使用 Node.js + TypeScript 写了一个 ADR 工具。现在,在我的大部分开源荐中,我都会使用它来管理一些技术决策。因为基于这个理论设计的这个文档系统真非常棒,我可以查询到:

  • 一个技术决策发生的时间和架构改变,对应的修改人
  • 回溯所有的技术决策,从中整理出架构发展过程
  • 所有的决策都是记录在版本控制系统中,可恢复
  • 易于管理和维护

对于一个长期开发的系统来说,它真的非常有用。

引子 2:静态站点生成:数据代码化

静态站点生成是一种混合式的 Web 开发方法,它通过部署预先构建的静态文件进行部署,来让开发者在本地构建基于服务器的网站。

当 GitHub Pages 成为了程序员首选的 博客/内容/文档 服务器时,他/她也采用了静态站点生成这一项技术。静态站点生成有各种各样的优点:

  • 可靠性、安全性、稳定性、可用性等更好
  • 可版本控制
  • 易于测试
  • 易于实践持续部署。提交即可上线
  • 灵活,易于定制

而事实上,静态站点生成所做的最主要的一件事是:将数据库中的数据进行代码化。采用诸如 Wordpress 这样的 CMS 时,我们是将数据存储在数据库中,以实现对于数据的 CRUD。一篇文章变为数据库二进制文件中的一个片段。

随后,静态站点生成工具做了第二件事情便是将文本内容可视化出来,便于人们阅读。这样一来,我们便实现了发布-开发分离。

引子 3:定制的标记语言:扩充

将数据代码化时,我们面临了一个非常大的挑战:易于编写、阅读的标记语言(如 markdown)只设计了内容的形式,缺少了内容相关的其它信息,诸如于创建时间、作者、修改时间等等。

于是各个静态站点生成器定制了自己的 markdown,添加了一些额外的信息,如 hexo 采用 :year-:month-:day-:title.md 的形式来管理文章的日期和标题等。这样一来说,就不需要通过读取这个文章的 Git 信息来构建出整个信息。

我们所熟悉的 GitHub Flavored Markdown 也是如此,通过不明显破坏内容格式的兼容模式来扩展 markdown 数据字段。

除此,我们可以定制基于 markdown 数据的图表、思维导图等内容。

引子 4:编辑-发布-开发分离:面向非技术人员

面向非技术人员设计是代码文档化的一大挑战。作为一个程序员,我们觉得 markdown 语法再简单不过了,但是对于非技术人员来说并非如此。他/她需要:一个易于上手的可视化编程器。而要实现这样一个目的,我们需要在架构上做一些转变,我们可以尝试使用 『编辑-发布-开发分离』 模式来解决这个问题。

即,我们将过程拆为了三步:

  • 编辑人员,可以使用常用的编辑器或者是定制的编辑器
  • 开发人员,编写内容的展示
  • 发布的时候,集成这两部分代码

我们依旧可以选择用源码管理的方式来管理内容。只需要将数据库接口,转变为 Git 服务器接口即可 —— 当然它们是稍有不同的。不过呢,把本地的 Git 转换为 Git remote 那就基本一致了。

如此一来,最后我们的成本就落在改造出一个基于 Git 的 markdown 编辑器。

文档代码化

完美,我又一次在引子里,把中心思想表达完了。

为什么你需要将文档代码化?

主要原因有:文档不代码化,就没有重构的可能性。

剩下的原因有:

  • 二进制的文档难以进行版本管理。想象一下 2020-02-30.docx2020-02-31.docx
  • 无法准确地知道谁是文档的修改者,大家可能都是 admin,又或者是会议上的张三
  • 找不到哪个是最新的文档
  • 文档写得很烂,但是你没办法重构二进制文档
  • 供应商绑定
  • ……

应该还有更多。

什么是文档代码化?

回到正题上:

文档代码化,将文档以类代码的领域特定语言的方式编写,并借鉴软件开发的方式(如源码管理、部署)进行管理。它可以借助于特定的工具进行编辑、预览、查看,又或者是通过专属的系统部署到服务器上。

它具备这么一些特征:

  • 使用标记语言编写内容。如 markdown
  • 可通过版本控制系统进行版本控制。如 git
  • 与编程一致的编程体验(除了内容写不了测试)

而一个高效的文档代码化系统,还具备这么一些特征:

  • 持续部署,即修改完内容可自动发布。
  • 与特定的形式组织内容索引。如以知识库的形式来组织内容。
  • 特定的文本格式。如架构决策记录、静态内容生成,以用于以提供更好的用户体验
  • 可支持 REST API。以通过编辑器来修改内容
  • 可以支持多种方式的输出。如网站标准 HTML,又或者是 Docx、Latex 等
  • 支持编辑、校对工作流
  • 支持搜索
  • 多人协作

而事实上,大部分的团队并不需要上述的高级功能,而且它们都已经有了成熟的方案。

如何设计一个文档代码化系统?

事实上,我们在四个引子中标明了我们所需要的要素:

  1. 使用格式化的文档
  2. 借助静态站点生成技术来发布系统
  3. 通过定制标记语言扩充能力
  4. 面向非技术人员实现编辑器

设计一个标记语言及其扩展语法,然后实现它即可。

1. 确立关键因素

考虑到我和我的同事们最近实现了这么一个系统,我还是忍受一下手的痛楚,简单说一下怎么做这样一个系统。我们所考虑的主要因素是:

  • 图表渲染
  • 流程图渲染
  • 可视化展示

因为由 DSL 转换成的图表易于修改,并且可以索引。于是乎,我们:

  1. 通过 markdown 的 Code 语法来扩充这些能力
  2. 使用 markdown 的 table 和 list 来提供数据
  3. 使用 D3.js 来支持流程图绘制
  4. 使用 Echarts 来进行图表绘制
  5. 尽量使用 SVG 图片
  6. ……

2. 实现一个 MVP

我们使用 Angular + GitHub,快速实现了一个 MVP:

  1. 我们使用 Git 作为数据库.它就可以实现多人协作的目的,并且可以追踪所有的变化
  2. 我们使用 GitHub Pages 作为服务器。只要一修改文档或者代码,就会部署最新的文档。
  3. 我们使用 marked.js,它可以让我们快速扩展语法。
  4. 使用 textarea 结合 markdown 制作了一个简易的编辑器。

随后,我们在这个基础上进行了快速的迭代。

3. 扩展语法

我们使用了 markdown 的 code 作为图表的 DSL,扩展了这么一些语法:

  • echarts。直接渲染 Echarts 图表
  • mindmap。Markdown List 转为思维导图
  • radar。Markdown List 转为雷达图
  • process-table。带流程的图表
  • process-step。另外一种带流程的图表
  • pyramid。金字塔图形
  • quadrant。四象限图
  • class。直接调用 CSS 的 class
  • graphviz。使用 Dot 渲染图片
  • mermaid。使用 mermaid 可视化
  • webcomponents。调用 WebComponents 组件
  • toolset。调用 Toolset 相关的组件

    • slider。权衡滑块
    • line-chart。表图

所以,对于使用者来说,只需要编写下面的代码:

  • 质量成熟度评估模型

    • 质量内建: 3 -> 4
    • 优化业务价值: 2 -> 2
    • 质量统一,可视化: 1 -> 5
    • 全员参与: 3 -> 4
    • 快速交付: 4 -> 5
    • 测试作为资产: 2 -> 3
    • 快速反馈: 5 -> 5

config: {"legend": ["当前", "未来"]}

就可以生成对应的图表:

image.png

又或者是用于制作技术雷达图:

image.png

我们还通过 config 来输入 JSON,进行一定的懒惰化处理(不要累死自己)。

3.1 重写 markdown 渲染器

我们在这个过程中,遇到的最大的挑战是,随着我们对 markdown 语法的不断扩充,相关的代码已经变成了一坨大泥球。所以,我们不得不重写了这部分代码:

  1. 借助于 marked.js 的 lexer 解析出 token
  2. 根据 token 修改生成新的 token
  3. 遍历新生成的 token,渲染出元素
  4. 结合虚拟滚动,解决性能问题

已经开源在 GitHub,并发布对应的 npm 包:@ledge-framework/render

4. 发布这个项目

我们已经在 GitHub 上发布了这个文档化系统,你可以参与到其中的使用和开发。

GitHub:https://github.com/phodal/ledge

项目首页:https://devops.phodal.com/

总结

然后,你就成为了一个 Markdown 工程师,D3.js 设计师,Echart 配置管理员。

查看原文

赞 8 收藏 5 评论 0

phodal 发布了文章 · 2020-03-31

Ledge:一个开源的『DevOps + 研发效能』知识平台

过去的三星期里,因为疫情 + 种种不可告人的原因,我开始建设一个 DevOps 知识平台。

GitHub:https://github.com/phodal/ledge/

在线使用:https://devops.phodal.com/

在这个知识平台里, 它包含了这么一些内容:

  • DevOps 工具元素周期表。帮助您进行数字化时代的 DevOps 工具选型。
  • DevOps 设计工具。帮助您设计组织内的 DevOps 流程,涵盖了流程、人、工具、制品等等。
  • 案例学习。从社区的知识库中,我们总结了传统企业走向 DevOps 的经验,并浓缩到易于使用的内容和材料中。
  • 最佳实践。我们从海量的 DevOps 内容中,提炼出了一系列的最佳实践,以更好地帮助企业进行 DevOps 实践。
  • 模式与原则。基于我们的实践,我们提炼了位于它背后的模式与原则,帮助个人和组织更好地了解 DevOps 文化。
  • 操作手册。只凭实践与原则,无法让中小型 IT 团队进行 DevOps 转型,所以我们准备了详实的操作手册,以帮助您一步步前进。
  • 度量。KPI - 度量、度量 - KPI、KPI - 度量,帮助您更好地度量 DevOps 转型情况。
  • 报告。我们尝试从丰富的 DevOps 报告中,提炼出有用的实践和工具。
  • Mobile DevOps。我们相信移动应用的 DevOps 改进,才是大多数公司的挑战。
  • 工具。工具,工具,工具是最好的生产力,工具比人的记忆力更加可靠。

起先,我是想做一些 DevOps 工具,比如说适合于中国国情的『DevOps 元素周期表』。顺带一说,这个工具不是我首创的,我只是用更好的架构实现了一遍。。如此一来,对于大部分开发人员来说,他/她们就可以从这个表中,组合出适合于自己组织的分子(毕竟周期表上都是原子)。几天之后,我就有了这个工具,根据整个研发体系的每一个过程,你可以从中挑选出适合你的要素:

DevOps 元素周期表

为了凑满上面的元素,我不得不找一个又一个大公司的案例,看看他们到底是用什么技术栈。所以,我七拼八凑得差不多了,顺便一想,既然我有这么多大公司的案例,为什么不抽象一下这些案例呢。于是就有了:

案例学习

我们从互联网的各个地方(来源见内容中标明的出处),帮你抽取了各大公司的案例:

  • 腾讯
  • 小米
  • 招商银行
  • 美团
  • ……

在这些案例,背后往往包含、隐藏了各种各样的价值取向。所以,进一步地,我想去提取这些模式,所以就有了:

模式与原则

包含了:

  • 流畅度模式
  • 度量体系设计
  • 学习型组织构建
  • ……

画完这些大包之后,随后,我们就可以进入 DevOps 的设计和实施阶段。我们要找到那些最好的实践:编程、团队、文化、能力、测试等等:

实践

太多,不写。 当然了,为了在组织中实施 DevOps,我们还需要一本操作手册,来帮助你一步步构建 DevOps 体系:

从度量,到实践,到工程化,再到流程打通,顺势而来,一步下实践。

工具

光有手册是不行的,我们还把各种各样的工具做了上去: 除了工具的名称,还包含:

  • 工具的准备事项
  • 工具的操作步骤
  • 工具的示例
  • 该工具的在线工具使用

还有更多的功能在开发中。 也欢迎加入我们的开发队伍,更多的案例将帮助每个人更好地成长。

GitHub:https://github.com/phodal/ledge/

在线使用:https://devops.phodal.com/

查看原文

赞 15 收藏 8 评论 0

phodal 分享了头条 · 2020-01-19

《系统重构与迁移指南》手把手教你分析、评估现有系统、制定重构策略、探索可行重构方案、搭建测试防护网、进行系统架构重构、服务架构重构、模块重构、代码重构、数据库重构、重构后的架构守护

赞 1 收藏 1 评论 0

phodal 发布了文章 · 2020-01-03

2019 节点: Love Wife & Change Life

为什么你还在 ThoughtWorks?

因为不加班。人生总会有很多的选择,在决策的那一刻,你不知道对与错。但是,开心就好。

12 月初,ThoughtWorks 开始了 Annual Review 的 Kick Off,我开始总结这一年的工作,与此同时,我也开始总结我的 2019 节点。今年仍然是『平淡无奇』也过完了重要的一年。

太长不读版:

  • 爱情上,领证了,和 @ 花仲马一起来到了杭州;还差好多钱买房,还得考虑办婚礼的事情。
  • 职业上,从深圳 office 转到了华东 MU,从华东 MU 转到了咨询团队,开始了在 TW 的出差生涯,还有加班生涯。
  • 设计上,每天画了一张画,一年 365 张画;插画,作为文章的一部分,已无处不在。
  • 写作上,出版了《前端架构:从入门到微前端》,印刷量在 7500 左右,有希望在一年内达到 1 万。
  • 编程上,写了更多的工具,愈加丰富的重构经验,顺带深入软件体系的架构。
  • 斜杠上,尝试电子产品的评测,写作相关的收入差不多是前两年之合。
  • 影响力上,开始了 International 的尝试 —— 时间仍然是一个限制因素。
  • 社交上,我退出了大量的微信群,专注于生产内容。

嗯,还有游戏,文明大法好。

所以,对比一下上一年的目标:

  1. 技术隐私,打造了自己的 Serverless 密码管理器:https://github.com/phodal/mopass ,作为一个 Chrome 插件,它很好地作为了我的二次管理认证工具。
  2. 非技术写作。好似没有一个开始,似乎也不是一个好的目标。借这个名义,我看完了《刺客信条》的小说。
  3. 工具。开发了更多、更有意思的工具,还有更多的 PoC。
  4. 设计。天天练习插画,更快的画画速度,质量上也有所提升。也烧了更多的钱绘画工具上。
  5. Coach。幸好在上一年里,它不是一个目标,扶不起的人太多了。对的人,对的事,才能成——借口。
  6. 影响力。受众级别比上一年有所提升,还有更深度的内容。

不算太好,也不算太糟糕。

编程:平台 + 工具 + DSL

惯例,依旧是工作 + 业余。

工作:Platform & Tools

工作上没有圈,也没有点,今年的工作简直是一团糟,还加了人生的第一次班,而明年还会有更多。Work–life balance 不断被打破,就得寻找一个更合适的地方 —— 如果有的话。

平台

上半年,工作的主要内容是大前端开发框架 / 平台,所以研究了一段时间低代码编程,写了那篇《无代码编程》的文章。一番操作下来,发觉重点在于 AST 和 DSL。因此,除了开发一些日常的工具之外,我开始撸 dilay 框架,创建了 subal 项目……。作为一个苦逼的 Tech Lead,除了项目相关的两个团队,还要照顾公司的其它多个团队。日常不是一般的忙,开会、开会、开会,还得做架构??还要评绩效??填别人的坑??还有写代码……

一个也不能落下,每个都落下了。

做了一个大前端开发平台,这样一折腾下来,收获倒也是挺大的,我对研发体系有了更深入的研究。考虑问题的时候,比以往更加系统,更加全面。文档、脚手架、示例应用、CLI 工具、IDE / 编辑器集成、售后 Q & A 等等一个都不能少。于是,在项目上写了对应的 CLI 工具,尝试把文档融入了开发工具中……

没毛病,老子可以各种吹了:不要做平台。我 Phodal 就是……,我也不会……。

工具

下半年,beach 了两三个月,写了个重量级应用 Inception,然后,转到了咨询团队。来到了新的 U,有了更多的灵感和时间去写工具,也从公司大佬新哥那获得一堆 Todo List。所以,下半年在业余时间写了更多的代码,写了更多的 DSL。所以,DSL 成为我这一年的一个主要风向。

我有了遗留系统重构工具: Coca,还有了 Badsmell 识别工具:Sprite,以及对应的重构建议工具:fanta ……。

它们都是使用 Go + Antlr 写的,target 是便宜的后端开发语言 Java。一顿瞎操作下来,除了更懂 Java 语法,我还学会了 Go。明年,我就可以 Rust + WebAssembly 搞 C or TypeScript 的语法分析了,一下子学会好几种东西的感觉好爽。

卧槽,又要兴奋的失眠了。想想,还是很美好的。

业余:工具 + DSL

2018 年底,我的 GitHub 数逼近 40,000;2019 年底,也有 48,615了,可不敢说逼近了。明年我的目标就是 50,000 star 的时候,发个朋友圈,哦,不对应该是 Twitter。

依旧的 Serverless 仍然是我的后端最佳选择,我用它写了我的密码管理工具:[moPass] https://github.com/phodal/mopass 。继 ADR 和 Phodit 之后,我的另外一个日常使用工具。我的业余项目上还上手了 Golang,嗯,真香。

Architecture

今年,有幸可以在项目中引入对于前端架构的探索,进一步地完善了我的前端架构体系,也产生了前端架构守护框架 Dilay,完美的造了个 PPT。

所以,在实践了 Domain Driven Design 和 Clean Architecture 之后,我开始思考 One Architecture 的可能性,尽管我已经用 JavaScript / TypeScript 证明了它的可能性:https://github.com/phodal/one

然而,Java 仍然是后端的主流语言,一个 Java 转 JavaScript 的工具不可缺少,而编程语言有那么多,所以我们需要的一个是 DSL 转任何语言的工具。也就是我最近在做的 Code 项目: https://github.com/phodal/code ,实践上还有待完善,只是 hello, world 出来。大抵,还需要半年地时间完善。

基准化

考虑到人总是会老的,Phodal 是人,所以 Phodal 会老的。我继续写工具、文章来沉淀知识,以用于以后甩出一个链接(装 x 神器):

对应的还有一篇相关的文章:《如何创建你的应用脚手架》,年轻就是好,对了,还有 Tech Lead 的基准化:《Tech Lead 的养成》。

Everything as Code:DSL

作为一个 Markdown 资深用户,除了进一步完善我的 Phodit,我还结合 Markdown 写了很多工具:

不过呢,定制别人的 DSL 始终是比较一个比较 hack 的方式,所以如何卓有成效地开发一个 DSL,便成为了一件非常有意思的事。所以,公司大佬说的 DSL as Data, Data as DSL 仍然是一个不错的目标。

在那篇《云开发:未来的软件开发方式》中,我提到了在未来几年,我要做的一些事情:

  • 更易于实践的微架构
  • 完善的代码化体系
  • 寻求合适的协作设计

所以,设计和抽象 DSL(Domain Specific Language)将成为了我未来几年一个重要战略。也因此,从大体上来说,它仍然是我的下一年目标和计划。

写作:100 万浏览量 + International

年初,出现了一个新的里程碑,我的博客 phodal.com 累计访问量突破了 1,000,000 万。

考虑到在微前端和 Clean Architecture 的实践,已经和国外的速度差不多,外加国内的 996 环境。所以在在今年年中,我尝试将 International 作为 Impact 的一个新方向。因此,在这篇总结里,我把写作相关的部分分为了大中华区和 International 区。

“大中华区”

虽然我在写新书的时候,看了很多小说,试图去改进,但是依旧在豆瓣上被吐槽『写出来太理论太像翻译腔』。没救了,没救了,写过 776 篇博客的我,表达能力依旧还有巨大地提升空间。

出版

今年 5 月出版的《[前端架构:从入门到微前端]》,出版社的总印刷数已经有 7500(并非卖完),豆瓣读书上的评分也有 7.6 分 —— 比前两本书多出了一份。瞬间又有动力准备下一本书了,只是怕是没有那么多时间了。

颇为遗憾的是,出于字数少的原因,我在『前端架构』 一书中多加了一个章节。而由于出版时间太早,少了后来实践的『Clean Architecture』——这是另外一个前端所需要的分层架构模式。将它与 Serverless 配合,就形成了我们所需要的 One Architecture。

文章:体系规划

从内容上来看,我对今年的文章倒是颇为满意的:

  • 《无代码编程》
  • 《整洁架构》
  • 《构建可信的软件架构 10 要素》
  • 《微前端架构》
  • 《管理依赖的 11 个策略》
  • 《云开发:未来的软件开发方式》
  • ……

但是如你所料,我创建了一篇又一篇地长文章,手就有点疼,坐久蛋也疼。

只是呢,好像也没有新的亮点了。

International

今年从我的观察来看,我在开源领域开始逐步走向非中文世界。Mooa 和 ADR ,迎来了一个又一个的国际友人的支持。我的 GitHub followers 也多了一个又一个的国际友人。

内容国际化,是今年年中开始的一个新的方向 —— 之前的另外一个国际化目标是:开源软件的国际化

虽然我的英语语法并不是那么靠谱,但是 Google Translate 也差。我相信同翻译腔一样,只要会被人吐槽英语语法不行,说明我已经成功引起大家地注意了。

English Articles

由于种种原因(诸如文章太长懒得翻译、高质量的文章不够多),产出仍然相对比较少。

不过,也算还行,我在 Dev.to 上创建了我的账号,发了一篇微前端相关的文章,还有一篇 Clean Architecture 相关的文章,也产生了一定的影响力。除了几十个的掌声,一万左右的单篇文章浏览量,还有 StackOverflow 有相关的问题指向了我的文章,笑而不语~。

所以,继续翻译更多的文章吧,是时候依赖反转一下了。

Review

过去的几年里,Review 英文书籍显然是国际赛道的一部分,只是呢,当时呢,这个 business line 还没想好,现在也没想好。

今年还是 Review 了 Packt 出版社的一本书籍《Web Development with Angular and Bootstrap - Third Edition》,遗憾的是近一二年 review 的书,都没有被引入国内。

不管怎样,国际化应当成为 2020 的一个继续前进的方向。

设计

我换了一个又一个的工具:

  • iPad + Apple Pencil。买前生产力,买后爱奇艺
  • Wacom Intuos Pro。专业级手绘板,相当的不错。
  • 绘王 Kamvas Pro 16。嗯,解控屏,效率就是高。对于我这种非专业级选手,还是非常好用的。
  • Wacom Intuos Mini。出差专用,个小板子虽然不那么好用,但是我也算是习惯了。

终于,我还是没画好画。

年中的时候,我尝试录制绘画的过程到 B 站、抖音上。但是,画的时候往往发现,录视频的时候,会影响我画画,也就作罢了。

画:365 天

上一年考虑到设计的边缘化,我开始采用日常练习的方式,来提升这方面的感觉。

Daily

最初设计的目标是每天 0.5 小时,但是受编程状态的影响,往往会被挤压到 15分钟。好在,随着练习的进一步 一深 深入,我还是能在短暂的时间内,画出一些不错的作品。

稍有不同的是,受出差的影响,我有时候不得不在早上画画。

Whatever,我已经有一堆画了。

所以,如果没有问题的话,它仍将作为我下一年的日常。问题的关键在于:如何结合文章的意图,创建对应的作品?

Design Thinking

寻找更广泛维度的设计思考。

TBD。

受限于有大量的代码要写,以及好像没有遇到好的设计师。没有灵感嘛,就先这样,慢慢来,路子还长着。万一明年可以遇到可以愉快合作的小伙伴呢。

其它

人生苦短,一点点做

Hello, 2020

嗯,我要登机回杭州了。

咦,2020 呢,要小康。

哦,不对,说好的婚礼呢。

本文参与了 SegmentFault思否征文「2019 总结」,欢迎正在阅读的你也加入。
查看原文

赞 3 收藏 0 评论 2

phodal 发布了文章 · 2020-01-02

遗留系统重构工具:Coca

好的代码是可以重构出来的。

如我在先前的文章所说,我最近的工作主要是在做架构重构、代码重构。所以,一如既往地,我又写了个工具来帮助我完成相关的工作。这样一来,下次我可以更快速地完成相关的工作。

在这之前,已经有大量的工具可以做类似的事情。如我司已有大佬开源了 Tequila ( https://github.com/newlee/teq... ) 这样的架构、依赖分析工具。只是呢,简单的架构分析是无法满足我的需求的。并且,本着写了工具就是赚经验的思想,我决定写一个自己的工具。

Coca 简介

从按我的实践经验来看,我将重构分为四种类型:

  • 分层架构重构。 在不改变业务逻辑的情况下,进代码架构进行调整。即根据单一职责和依赖倒置原则的思想,对系统进行模块拆分与合并,以明确职责降低耦合度;对包进行重新规划,划分包之间的边界,减少代码间的耦合。
  • 模式重构。针对特定模式的坏味道,采用设计模式来提升可扩展性,增加可读性。
  • 模型重构。在包含测试的情况下,通过识别和发现模型的行为,将行为聚合到模型中。
  • 微重构。对于一些小的代码坏味道,可以通过 IDE 重构来快速改善即有代码,而不会影响到业务功能。

而《重构:改善既有代码的设计》一书主要针对的是微重构。因为重构项目的难度不是一般的大,对于经验不足的个人、团队来说,重写往往比重构来得便捷。

所以,根据我的需要我写了自己的工具,以用于改善即有代码的设计:

Coca 是一个用于遗留系统重构的瑞士军刀。它可以分析代码中的 badsmell,行数统计,分析调用与依赖,进行 Git 分析,以及自动化重构等。

GitHub 地址:https://github.com/phodal/coca

安装:

  1. go get -u github.com/phodal/coca
  2. 直接从 GitHub 上下载可执行文件

在执行所有的命令之前,需要先执行 coca analysis 以生成对应的依赖关系数据。

So,让我们看看 Coca 1.0 包含了哪些功能?

API 调用

这个功能目前主要针对的是 Spring 开发的,它似乎已经是 Java 世界的后端服务的标准。

REST API 生成

只需要执行 coca api 就可以生成我们想要的结果:

  1. 函数调用图
  2. API 调用层级

下图是 API 的统计信息:

SIZEMETHODURICALLER
36GET/aliyun/oss/policycontroller.OssController.policy
21POST/aliyun/osscallbackcontroller.OssController.callback
17GET/subject/listcontroller.CmsSubjectController.getList
17GET/esProduct/searchsearch.controller.EsProductController.search
17GET/order/listcontroller.OmsOrderController.list
17GET/productAttribute/list/{cid}controller.PmsProductAttributeController.getList
17GET/productCategory/list/{parentId}controller.PmsProductCategoryController.getList
17GET/brand/listcontroller.PmsBrandController.getList
17GET/esProduct/search/simplesearch.controller.EsProductController.search

对应的全景图:

API Call

当然了,你也可以轻松通过参数过滤一下你想要的内容。

这个功能的目标是用于未来向 DDD 重构时,构建出限界上下文。

调用关系图

也可以只看某一部分的依赖关系图:

coca call -c com.phodal.pholedge.book.BookController.createBook -r com.phodal.pholedge.

输入对应的完整方法名,和想要去除的包含即可:

Method Call

反向依赖关系图

还能生成对应的反向调用关系图:

coca rcall -c org.bytedeco.javacpp.tools.TokenIndexer.get

结果如下图所示:

Cocal Rcall

行为分析

由于代码只是反应系统的另一部分,我们不得不从版本管理工具中获取更多的信息,于是有了:

coca git

文件修改统计

排名靠前的文件,可以帮我们看到一些问题:coca git -t,于是乎,我们就有了:

ENTITYNAMEREVSCOUNTAUTHORCOUNT
build.gradle132636
src/asciidoc/index.adoc23920
build-spring-framework/resources/changelog.txt18710
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java17010
spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java15915

对,这是 Spring Framework 中最常修改的文件,前面三个文件看上去是合理的,但是 AnnotationUtils.java 显然有问题。

对应的 DefaultListableBeanFactory.java 也有 2000+ 行左右的规模。

从代码的行数和修改次数来看,它们都是上帝类,并且经常出现 Bug。

代码年龄统计

嗯,还有诸如于 coca git -a 这样的普通功能:

+-------------------------------------------------------------------------------------------------------------------------+-------+
|                                                       ENTITYNAME                                                        | MONTH |
+-------------------------------------------------------------------------------------------------------------------------+-------+
| helloworld.go                                                                                                           |  2.04 |
| README.md                                                                                                               |  2.04 |
| imp/imp.go                                                                                                              |  2.04 |
| .gitignore                                                                                                              |  2.01 |
| cmd/root.go                                                                                                             |  2.01 |
| test/coca_suite_test.go                                                                                                 |  1.94 |
| learn_go_test.go                                                                                                        |  1.94 |
| core/languages/java/JavaLexer.tokens                                                                                    |  1.84 |
| core/languages/java/java_lexer.go                                                                                       |  1.84 |
| examples/step2-Java/domain/ValueObjectD.java                                                                            |  1.84 |

其它功能

还有诸如基本的分析功能等: coca git -b

+-----------+--------+
| STATISTIC | NUMBER |
+-----------+--------+
| Commits   |    350 |
| Entities  |    514 |
| Changes   |    255 |
| Authors   |      2 |
+-----------+--------+

有兴趣可以自己去探索。

知识提炼

嗯,这也是我计划在明年实践的功能。

方法提取

作为此功能的第一步,我想的是先从代码中提取单词:coca concept

+------------------+--------+
|      WORDS       | COUNTS |
+------------------+--------+
| context          |    590 |
| resolve          |    531 |
| path             |    501 |
| content          |    423 |
| code             |    416 |
| resource         |    373 |
| property         |    372 |
| session          |    364 |
| attribute        |    349 |
| properties       |    343 |
| headers          |    330 |
+------------------+--------+

作为第一个简单的版本,我只是做了分词和统计,下一步便是专业性的统计。而后,用于生成领域专用的特定语言。

提交信息提取

你懂的,这里面的信息才是足够的丰富。

TBD

提取中文注释

下一步,我应该做类似的事情,哈哈哈

坏味道识别

这是一个非常通用的功能,你可以在各种各样的工具里找到。所以,我并没有特意地去增强里面的功能,也没有添加太多的功能——因为我知道他们比我的工具专业。

只需要:coca bs 就会得到一个建议修改的 JSON 文件:

{
   "lazyElement": [
      {
         "File": "examples/api/model/BookRepresentaion.java",
         "BS": "lazyElement"
      }
      ...
   ]
}

结合我写的 fanta 工具,可以生成对应的修改建议。

批量重构

主要是用于结合上述工具的分析结果,通过人工 + 智能的方式来实现批量化自动修正。

当前 API 处于试验阶段,请不要在生产环境使用。支持以下功能:

  • 批量重命名
  • 批量移动文件
  • 批量删除未使用的 import
  • 批量删除未使用的类

使用的方式也非常简单:

coca refactor -m move.config -p .

你可以试试,哈哈

代码统计分析

代码统计工具,采用的是 SCC:

scc is a very fast accurate code counter with complexity calculations and COCOMO estimates written in pure Go

愉快地: coca cloc,然后:

───────────────────────────────────────────────────────────────────────────────
Language                 Files     Lines   Blanks  Comments     Code Complexity
───────────────────────────────────────────────────────────────────────────────
Go                          58     31763     7132       890    23741       2847
Java                        44       971      208        21      742         62
Markdown                     8       238       75         0      163          0
Gherkin Specificati…         2        32        2        16       14          0
Document Type Defin…         1       293       36         0      257          0
License                      1       201       32         0      169          0
SQL                          1         2        0         0        2          0
SVG                          1       199        0        34      165          0
Shell                        1         3        1         1        1          0
XML                          1        13        0         0       13          0
gitignore                    1        61        8         4       49          0
───────────────────────────────────────────────────────────────────────────────
Total                      119     33776     7494       966    25316       2909
───────────────────────────────────────────────────────────────────────────────
Estimated Cost to Develop $803,822
Estimated Schedule Effort 14.120551 months
Estimated People Required 6.743156
───────────────────────────────────────────────────────────────────────────────

其它功能可以见 scc 工具的官方文档。

重构适合度评估

TBD

其它

这是我第一个使用 Golang 写的工具,希望我的用法足够的 Go Style。

首页:https://coca.migration.ink/

GitHub 地址:https://github.com/phodal/coca

愉快地:go get -u github.com/phodal/coca

查看原文

赞 2 收藏 0 评论 0

phodal 赞了文章 · 2019-11-27

使用 Angular 打造微前端架构的 ToB 企业级应用

这篇文章其实已经准备了11个月了,因为虽然我们年初就开始使用 Angular 的微前端架构,但是产品一直没有正式发布,无法通过生产环境实践验证可行性,11月16日我们的产品正式灰度发布,所以是时候分享一下我们在使用 Angular 微前端这条路上的心得(踩过的坑)了额,希望和 Angular 社区一起成长一起进步,如果你对微前端有一定的了解并且已经在项目中尝试了可以忽略前面的章节。

什么是微前端

微前端这个词这两年很频繁的出现在大家的视野中,最早提出这个概念的应该是在 ThoughtWork 的技术雷达,主要是把微服务的概念引入到了前端,让前端的多个模块或者应用解耦,做到让前端的子模块独立仓储,独立运行,独立部署。

那么微前端和微服务到底有什么区别呢?

下面这张图是微服务的示意图,微服务主要是业务模块按照一定的规则拆分,独立开发,独立部署,部署后通过 Nginx 做路由转发,微服务的难点是需要考虑多个模块之间如何调用的问题,以及鉴权,日志,甚至加入网关层

image.png

对于微服务来说,模块分开解藕基本就完事了,但是微前端不一样,前端应用在运行时却是一个整体,需要聚合,甚至还需要交互,通信。

image.png

为什么需要微前端(Micro Front-end)

  1. 系统模块增多,单体应用变得臃肿,开发效率低下,构建速度变慢;
  2. 人员扩大,需要多个前端团队独立开发,独立部署,如果都在一个仓储中开发会带来一些列问题;
  3. 解决遗留系统,新模块需要使用最新的框架和技术,旧系统还继续使用。

微前端的几种方案对比

image.png

上述只是简单列举了几种实现方式的对比,当然这些方案也不是互斥的,选择哪种方案取决你的业务场景是什么,以下几个前提条件对于技术选型至关重要:

  • 是否为 SPA 单体应用?
  • 技术栈是否统一,需要支持跨框架调用吗?
  • 是否需要应用间彻底隔离?

我们是做企业级 SaaS 平台的,肯定是 SPA 单体应用,技术栈都是 Angular,应用之间不需要彻底隔离,反而需要共享通用样式和组件,避免重复加载。

所以选择的是:运行时组合 方案。

Worktile 的微前端技术选型之路

目前市面上的微前端解决方案并不多,关注度和成熟度最高的应该就是 single-spa

国内也有很多团队都有自己的微前端框架,比如开源了的基于 single-spa 的 qiankun - 可能是你见过最完善的微前端解决方案 , 还有 phodal 的 mooa 以及无数内部的解决方案(最近阿里飞冰也开源 了面向大型工作台的微前端解决方案 icestark,只支持 React 和 Vue)

我们在做技术选型的时候首要考虑的就是 single-spamooa, single-spa 成熟度应该最高,示例文档很完善,mooa 为 Angular 打造的主从结构的微前端框架,和我们的业务和技术符合度最高,研究一段时间后最终我们还是选择了自研一套符合自己的微前端库(因为比较简单,不敢称之为框架),主要是因为我们的业务有以下几个需求在以上的框架中不满足或者说很难满足, 甚至需要高度定制。

  • 产品是主从结构的,Portal 包含左侧导航,消息通知以及子应用管理
  • 需要在多个子应用之间通信,主应用或者某个子应用需要打开其他子应用的详情页或者路由跳转
  • 子应用A的某个页面中可能会加载子应用B的某个组件
  • 基于以上2个特性,所以需要提供并存模式,即当前显示的虽然是 B 应用,但是要保证 A 应用正常可以调用,如果销毁了就无法被其他应用调用
  • 需要提供预加载功能
  • 子应用的样式也需要独立加载
  • 路由,不管是在主应用还是子应用,路由体验要和单体应用一致

我运行了 single-spamooa 的示例,主要是一些简单的渲染展示,一旦需要满足以上一些特性还是需要修改很多东西,mooa 实现应该还是比较全面也比较适合我们的,但是它的示例中路由有一些问题,页面跳转了但是路由没有变,打包已经抛弃了 Angular CLI,代码层面参考了 single-spa 的很多东西,API 可以再度简化,既然是为 Angular 定制的,我觉得应该以 Angular 的方式实现更符合,当然不排除作者想要后期支持 React 和 Vue,不可否认的是 phodal 本人对于微前端的理解的确很深,写的很多不错的微前端的文章 microfrontends, 甚至出过唯一一本微前端的书《前端架构 - 从入门到微前端》,我在实现微前端的时候也借鉴参考了它的很多思想和实现方式。

使用 Angular 打造微前端应用

使用 Angular 实现微前端其实比 React 和 Vue 更加困难,因为 Angular 包含 AOT 编译,Module,Zone.js ,Service 共享等等问题,React 和 Vue 直接子应用 JS 加载渲染页面某个区域即可。

选择动态加载模块后编译还是加载整个应用

在 Angular 单体应用中,必须有一个根模块 AppModule,然后是每个特性模块 FeatureModule,每个特性模块可以有自己的路由,当然可以使用路由的惰性加载这些特性模块,但是在微前端架构中,每个子模块都是独立仓储的,如何在运行时把子模块加载到根模块就是一个技术选择难点。

  1. 第一种方案就是把每个子模块当作一个特性模块,然后在打包的时候随着主应用一起打包编译,这样是最简单的,但是这个无法做到独立部署,而且每次部署都是全量更新
  2. 第二种方案还是把子模块当作一个特性模块,在主应用通过 SystemJsNgModuleLoader 加载子模块,然后编译运行,(注:SystemJsNgModuleLoader 在新版本已经遗弃)
  3. 第三种方案就是每个子模块是一个独立的应用,和主应用一样,有自己的 AppModule, 路由,选择这种方案就需要处理多个应用路由同步的问题,还有就是 Angular 目前的依赖库是无法直接运行时使用的,需要每个子应用一起编译,无法做到公共依赖库抽取(可能有其他方案)
  4. 第四种方案就是把所有的子模块编译成 Web Components 使用,我暂时没有深入研究过,选择这种方案直接使用组件肯定没有问题,但是使用 Web Components 后路由如何处理我不知道。

我们最终选择了最复杂的第三种方案,因为新的 Ivy 渲染引擎正式发布后会解决第三方依赖库运行时直接使用的问题,至于 Web Components 没有深入研究,因为目前第三种方案运行挺好的。

image.png

应用注册,加载,销毁机制

这个是所有微前端应用的基础和核心,但是我觉得反而是最简单容易实现的,主要要做的就是:

  • 提供静态资源动态加载功能
  • 配置好子应用的规则,包含:应用名称,路由前缀,静态资源文件
this.planet.registerApps([
    {
        name: 'app1',
        hostParent: '#app-host-container',
        routerPathPrefix: '/app1',
        selector: 'app1-root',
        scripts: ['/static/app1/main.js'],
        styles: ['/static/app1/styles.css']
    },
   // ...
]);
  • 应用加载:根据当前页面的 URL 找到对应的子应用,然后加载应用的静态资源,调用预定义好的启动函数直接启动应用即可,在 Angular 中就是启动根模块 platformBrowserDynamic().bootstrapModule(AppModule)
  • 应用的预加载:当前应用渲染完毕会预加载其他应用,并启动,并不会显示
  • 销毁应用使用 appModuleRef.destroy();

按照上述的步骤处理简单的场景基本就足够了,但是如果希望应用共存就不一样了,我们的做法是把 bootstrapped 状态隐藏起来,而不是销毁,只有 Active 状态的应用才会显示在当前页面中。

路由

因为选择了每个子应用是独立的 Angular 应用,同时还可以共存多个子应用,那么多个应用的路由同步,跳转就成了难题,而且还要支持应用之间路由跳转,应用之间通信,组件渲染等场景。我认为路由是我们在使用微前端架构中遇到的最复杂的问题。

目前我们的做法是主应用的路由中把所有子应用的路由都配置上,组件设置成 EmptyComponent , 这样在切换到子应用路由的时候,主应用会匹配空路由状态,不会报错,每个子应用需要添加一个通用的空路由 EmptyComponent

{
        path: '**',
        component: EmptyComponent
}

除此之外还需要在切换路由的时候同步更新其他应用的路由,否则会造成每个应用的当前路由状态不一致,切换的时候会有跳转不成功的问题。

  • 主应用路由切换时,找到所有当前启动的子应用,使用 router.navigateByUrl 同步跳转
  • 子应用路由切换时,同步主应用路由,同时同步其他启动状态的子路由

我看了很多微前端框架包括 single-spa,基本上路由这一块没有处理,完全交给开发者自己去填坑,single-spa 的 Angular 示例基本就是切换就销毁了 Angular 应用,因为没有并存,所以也就不需要处理多个应用路由的问题了,当然它作为和框架无关的微前端解决方案,也只能做到这一步了吧。

这个等 Ivy 渲染引擎正式发布后,可以把子应用编译成直接可以运行的模块,整个应用如果只有一个路由会简化很多。

共享全局服务

对于一些全局的数据我们一般会存储在服务中,然后子应用可以直接共享,比如:当前登录用户多语言服务等,简单的数据共享可以直接挂载在 window 上即可,为了让每个子应用使用全局服务和模块内服务一致,我们通过在主应用中实例化这些服务,但后在每个子应用的 AppModule 中使用 provide 重新设置主应用的 value,当然这些不需要子应用的业务开发人员自己设置,已经封装到业务组件库中全局配置好了。

{
  provide: AppContext,
  useValue: window.portalAppContext
}

应用间通信

应用间通信有很多中方式,我们底层使用浏览器的 CustomEvent ,在这之上封装了 GlobalEventDispatcher 服务做通信(当然你也可以使用在 window 对象上挂载全局对象实现),场景就是某个子应用要打开另外一个子应用的详情页

// App1
globalEventDispatcher.dispatch('open-task-detail', { taskId: 'xxx' });

// App2
globalEventDispatcher.register('open-task-detail').subscribe((payload) => {
    // open dialog of task detail
});

应用间组件互相调用

在我们的敏捷开发子产品中,一个用户故事的详情页,需要显示测试管理应用的关联的测试用例和测试执行情况,那么这个测试用例列表组件放在 测试管理 子应用是最合适的,那么用户故事详情页肯定在敏捷开发应用中,如何加载测试管理应用的某个组件就是一个问题。

这一块使用了 Angular CDK 中的 DomPortalOutlet 动态创建组件,并指定渲染在某个容器中,这样保证了这个动态组件的创建还是 测试管理 模块的,只是渲染在了其他应用中而已。

const portalOutlet = new DomPortalOutlet(container, componentFactoryResolver, appRef, injector);
const testCasesPortalComponent = new ComponentPortal(TestCasesComponent, null);
portalOutlet.attachComponentPortal(testCasesPortalComponent);

工程化

使用微前端开发应用不仅仅要解决 Angular 的技术问题,还有一些开发,协作,部署等工程化的问题需要解决,比如:

  • 公共依赖库抽取
  • 本地如何启动开发
  • 如何打包部署,生成的 hash 资源文件如何通知主应用

应用公共依赖库抽取避免类库重复打包,减少打包体积,这就需要自定义 Webpack Config 实现,起初我们是完全自定义 Webpack 打包 Angular 应用,一旦这么做就会失去很多 CLI 提供的方便功能,偶尔发现了一个类库 angular-builders ,他的作用其实就是在 Angular CLI 生成的 Webpack Config 中合并自定义的 Webpack Config,这样就做到了只需要写少量的自定义配置,其余的还是完全使用 CLI 的打包功能,差一点就要自己写一个类似的工具了。
在主应用中把需要公共依赖包放入 scripts 中,然后在子应用中配置 externals,比如:momentlodashrxjs 这样的类库。

const webpackExtraConfig = {
    optimization: {
        runtimeChunk: false // 子应用一定要设置 false,否则会报错
    },
    externals: {
        moment: 'moment',
        lodash: '_',
        rxjs: 'rxjs',
       'rxjs/operators': 'rxjs.operators',
        highcharts: 'Highcharts'
    },
    devtool: options.isDev ? 'eval-source-map' : '',
    plugins: [new WebpackAssetsManifest()]
};
return webpackExtraConfig;

WebpackAssetsManifest 主要作用是生成 manifest.json 文件,目的就是让生成的 Hash 文文件的对应关系,让主应用加载正确的资源文件。

本地开发配置 proxy.conf.js 代理访问每个子应用的资源文件,同时包括 API 调用。

基于 Angular 的微前端库 ngx-planet

以上是我们在使用 Angular 打造微前端应用遇到的一些技术难点和我们的解决方案,调研后最终选择自研一套符合我们业务场景的,同时只为 Angular 量身打造的微前端库。

Github 仓储地址:ngx-planet
在线 Demo:http://planet.ngnice.com

不敢说 “你见过最完善的微前端解决方案” ,但至少是 Angular 社区目前我见过完全可用于生产环境的方案,API 符合 Angular Style ,国内很多大厂做微前端方案基本都忽略了 Angular 这个框架的存在,Worktile 四个研发子产品完全基于 ngx-planet 打造开发,经过接近一年的踩坑和实践,基本完全可用。

image.png

希望 Angular 社区可以多一些微前端的解决方案,一起进步,我们的方案肯定也存在很多问题,也欢迎大家提出改进的建议和吐槽,我们也将继续在 Angular 微前端的路上继续深耕下去,如果你正在寻找 Angular 的微前端类库,不妨试试 ngx-planet。

将来会调研在 Ivy 渲染引擎下的优化和改进方案。

Worktile 官网:worktile.com

本文作者:Worktile高级工程师 徐海峰

文章首发于「Worktile官方博客」,转载请注明出处。

查看原文

赞 10 收藏 4 评论 0

phodal 赞了文章 · 2019-08-20

微前端如何落地?

写在前面:本文节选自ThoughtWorks 黄峰达《前端架构:从入门到微前端》一书。这是一本围绕前端架构的实施手册,从基础的架构规范,到如何设计前端架构,再到采用微前端架构拆分复杂的前端应用。通过系统地介绍前端架构世界的方方面面,来帮助前端工程师更好地进行系统设计。

微前端如何落地?

在过去的几个星期里,随着 Martin Fowler 博客上那篇 Cam Jackson 写的微前端的文章发布,到处都在讨论 Microfrontend。作为一个微前端 “专家”,我也分享一下:如何去落地微前端。

微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时,它们也可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。

为什么需要微前端?

微前端不是银弹,它和微服务一样会带来大量的挑战。

  • 遗留系统迁移。解决遗留系统,才是人们采用微前端方案最重要的原因
  • 聚合前端应用。微服务架构,可以解耦后端服务间依赖。而微前端,则关注于聚合前端应用。
  • 热闹驱动开发。新的技术,既然很热闹,那么就学吧。

微前端的实现,意味着对前端应用的拆分。拆分应用的目的,并不只是为了架构上好看,还为了提升开发效率。

为此,微前端带来这么一系列的好处:

1.应用自治。只需要遵循统一的接口规范或者框架,以便于系统集成到一起,相互之间是不存在依赖关系的。

2.单一职责。每个前端应用可以只关注于自己所需要完成的功能。

3.技术栈无关。你可以使用 Angular 的同时,又可以使用 React 和 Vue。

除此,它也有一系列的缺点:

  • 应用的拆分基础依赖于基础设施的构建,一旦大量应用依赖于同一基础设施,那么维护变成了一个挑战。
  • 拆分的粒度越小,便意味着架构变得复杂、维护成本变高。
  • 技术栈一旦多样化,便意味着技术栈混乱

毕竟没有银弹。

如何设计微前端架构?

就当前而言,要设计出一个微前端应用不是一件容易的事——还没有最佳实践。在不同的落地案例里,使用的都是不同的方案。出现这种情况的主要原因是,每个项目所面临的情况、所使用的技术都不尽相同。为此,我们需要了解一些基础的微前端模式。

架构模式

微前端应用间的关系来看,分为两种:基座模式(管理式)、自组织式。分别也对应了两者不同的架构模式:

  • 基座模式。通过一个主应用,来管理其它应用。设计难度小,方便实践,但是通用度低。
  • 自组织模式。应用之间是平等的,不存在相互管理的模式。设计难度大,不方便实施,但是通用度高。

就当前而言,基座模式实施起来比较方便,方案上便也是蛮多的。

而不论种方式,都需要提供一个查找应用的机制,在微前端中称为服务的注册表模式。和微服务架构相似,不论是哪种微前端方式,也都需要有一个应用注册表的服务,它可以是一个固定值的配置文件,如 JSON 文件,又或者是一个可动态更新的配置,又或者是一种动态的服务。它主要做这么一些内容:

  • 应用发现。让主应用可以寻找到其它应用。
  • 应用注册。即提供新的微前端应用,向应用注册表注册的功能。
  • 第三方应用注册。即让第三方应用,可以接入到系统中。
  • 访问权限等相关配置。

应用在部署的时候,便可以在注册表服务中注册。如果是基于注册表来管理应用,那么使用基座模式来开发比较方便。

设计理念

在笔者实践微前端的过程中,发现了以下几点是我们在设计的过程中,需要关注的内容:

  • 中心化:应用注册表。这个应用注册表拥有每个应用及对应的入口。在前端领域里,入口的直接表现形式可以是路由,又或者对应的应用映射。
  • 标识化应用。 我们需要一个标识符来标识不同的应用,以便于在安装、卸载的时候,能寻找到指定的应用。一个简单的模式,就是通过康威定律来命名应用。
  • 应用生命周期管理。
  • 高内聚,低耦合。

生命周期

前端微架构与后端微架构的最大不同之处,也在于此——生命周期。微前端应用作为一个客户端应用,每个应用都拥有自己的生命周期:

  • Load,决定加载哪个应用,并绑定生命周期
  • bootstrap,获取静态资源
  • Mount,安装应用,如创建 DOM 节点
  • Unload,删除应用的生命周期
  • Unmount,卸载应用,如删除 DOM 节点、取消事件绑定

这部分的内容事实上,也就是微前端的一个难点所在,如何以合适的方式来加载应用——毕竟每个前端框架都各自不同,其所需要的加载方式也是不同的。当我们决定支持多个框架的时候,便需要在这一部分进入更细致的研究。

如何拆分?

随后,我们要面临的一个挑战是:如何去拆分应用。

技术方式

从技术实践上,微前端架构可以采用以下的几种方式进行:

  • 路由分发式。通过 HTTP 服务器的反向代理功能,来将请求路由到对应的应用上。
  • 前端微服务化。在不同的框架之上设计通讯、加载机制,以在一个页面内加载对应的应用。
  • 微应用。通过软件工程的方式,在部署构建环境中,组合多个独立应用成一个单体应用。
  • 微件化。开发一个新的构建系统,将部分业务功能构建成一个独立的 chunk 代码,使用时只需要远程加载即可。
  • 前端容器化。通过将 iFrame 作为容器,来容纳其它前端应用。
  • 应用组件化。借助于 Web Components 技术,来构建跨框架的前端应用。

实施的方式虽然多,但是都是依据场景而采用的。有些场景下,可能没有合适的方式;有些场景下,则可以同时使用多种方案。

路由分发式

路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。如下图所示:

就当前而言,路由分发式的架构应该是采用最多、最容易的 “微前端” 方案。但是

前端微服务化

前端微服务化,是微服务架构在前端的实施,每个前端应用都是完全独立(技术栈、开发、部署、构建独立)、自主运行的,最后通过模块化的方式组合出完整的前端应用。其架构如下图所示:

采用这种方式意味着,一个页面上同时存在二个及以上的前端应用在运行。而路由分发式方案,则是一个页面只有唯一一个应用。

组合式集成:微应用化

微应用化,即在开发时,应用都是以单一、微小应用的形式存在,而在运行时,则通过构建系统合并这些应用,组合成一个新的应用。其架构如下图所示:

微应用化更多的是以软件工程的方式,来完成前端应用的开发,因此又可以称之为组合式集成。对于一个大型的前端应用来说,采用的架构方式,往往会是通过业务作为主目录,而后在业务目录中放置相关的组件,同时拥有一些通用的共享模板。

微件化

微件(widget),指的是一段可以直接嵌入在应用上运行的代码,它由开发人员预先编译好,在加载时不需要再做任何修改或者编译。而微前端下的微件化则指的是,每个业务团队编写自己的业务代码,并将编译好的代码部署(上传或者放置)到指定的服务器上,在运行时,我们只需要加载相应的业务模块即可。对应的,在更新代码的时候,我们只需要更新对应的模块即可。下图便是微件化的架构示意图:

在非单面应用时代,要实现微件化方案,是一件特别容易的事。从远程加载来对应的 JavaScript 代码,在浏览器上执行,生成对应的组件嵌入到页面的相应部分。对于业务组件也是类似的,提前编写好我们的业务组件,当需要对应的组件时再响应、执行。

前端容器化

前端容器:iframe

iframe 作为一个非常 “古老” 的,人人都觉得普通的技术,却一直很管用。它能有效地将另一个网页/单页面应用嵌入到当前页面中,两个页面间的 CSS 和 JavaScript 是相互隔离的——除去 iframe 父子通信部分的代码,它们之间的代码是完全不相互干扰的。iframe 便相当于是创建了一个全新的独立的宿主环境,类似于沙箱隔离,它意味着前端应用之间可以相互独立运行。

结合 Web Components 构建

Web Components 是一套不同的技术,允许开发者创建可重用的定制元素(它们的功能封装在代码之外),并且在 web 应用中使用它们。

目前困扰 Web Components 技术推广的主要因素,在于浏览器的支持程度。在 Chrome 和 Opera 浏览器上,对于 Web Components 支持良好,而对于 Safari、IE、Firefox 浏览器的支持程度,并没有那么理想。

业务拆分

与微服务类似,要划分不同的前端边界,不是一件容易的事。就当前而言,以下几种方式是常见的划分微前端的方式:

  • 按照业务拆分。
  • 按照权限拆分。
  • 按照变更的频率拆分。
  • 按照组织结构拆分。利用康威定律来进一步拆分前端应用。
  • 跟随后端微服务划分。实践证明, DDD 与事件风暴是一种颇为有效的后端微前端拆分模式,对于前端来说,它也颇有有效——直接跟踪后端服务。

每个项目都有自己特殊的背景,切分微前端的方式便不一样。即使项目的类型相似,也存在一些细微的差异。

微前端之外

如果微前端对于你们来说困境重重,还有一些不错的架构模式可以采用。

应用微化架构

应用微化架构,是一种开发时整体,构建时拆分,运行时分离的前端架构模式。即应用微化架构从一份代码中,构建出适用于不同环境的多套目标代码。实现上它是一种:

  • 构建时拆分架构。
  • 代码删除架构。笑,以删代码的方式,来形成每个前端应用。
  • 微前端准备式架构。即,随时可以拆分为多个前端应用。

由于它与微应用化的相似性,我们将它与微应用化做一个对比。它与微应用化不同的是,应用微化是在构建时对应用进行拆分,而非在本地模式下对应用拆分。相似的是,它也是基于构建系统的应用拆分方式。

即:微应用化,是一个随时可合并式架构。而应用微化,则是一个随时可拆分式架构。

它不仅仅是一个适合于前端的架构模式,也是一适用于后端的架构模式。

整洁前端架构

Clean Architecture 是由 Robert C. Martin 在 2012 年提出的架构模式。它具有这么一些特点:框架无关性、可被测试、UI 无关性、数据库无关性、外部机构(agency)无关性。

对于前端架构来说,Clean Architecure 实际上是:Clean Architecture + MVP + 组件化。如下图所示:

考虑到应用的规模,这里以 Angular + TypeScript 作为示例:

这种架构模式特别适合于:组织内即写前端又同后端的团队。它易于映射前后端 API,且可以使用 usecase 作为防腐层。

没有银弹。不得不提及的是,对于小规模的团队来说,它带来的弊端可能会远远大于好处——带来大量冗余的代码。尽管通过 Angular Schematics 可以通过参数来生成代码,但是这种分层架构地于简单的应用来说,还是过于复杂、难以上手。对于不写测试的项目来说 ,usecase 也不能带来它们所承诺的好处。

结论

微前端不是银弹,当前也没有最佳实践,但是这是一个非常好的学习机会。


文/ThoughtWorks黄峰达

更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

查看原文

赞 20 收藏 15 评论 0

phodal 发布了文章 · 2019-07-05

前端架构,有什么能做的?

前端有架构吗?前端有架构模式吗?

架构是什么?

软件架构,是一种为了解决复杂问题的通用模式。软件架构,是关于软件系统的一系列有层次的技术决策的集合。换句话来说,当我们讨论架构的时候,不能只讨论某某架构,而是要包含其实施,以及后期的维护。

因为:

  • 一个无法上线的应用架构,算不上是好的软件架构
  • 一个没有人能完成开发的软件架构,算不上是可行的软件架构
  • 一个在现有的技术上不可行的架构,算不上是合理的软件架构

诸如微服务,对于复杂的后端系统来说,是一种不错的『低耦合,高内聚』的实施。但是,它并不适合于大部分的小公司——复杂的架构不适合于简单的应用。而小公司也缺乏足够的人才,来实施一个复杂的系统,与此同时还需要有人能维护这个系统。

所以,当我们谈及软件架构的时候,说的是:有这么一些人,他/她们能按时、高质量(或者说有质量)完成这一个系统的设计——即有能力的个人

PS:对于前端架构来说,这些人大概率会来自于看了本书的人,笑~

前端架构拆解:四层次设计

从笔者的角度来看,架构设计本身是分层级的,面向不同级别的人时,所展示的内容也是不一样的。如面对的是同一级别、更高一级别的架构师、技术人员,说的便是形而上学的东西,如微前端、前后端分离,并通过各种概念,如构建系统拆份,以抽象的方式介绍如何去设计。这些概念,对于接触过的程序员来说,也是相当好理解的。而当我们面对的是,经验略微丰富的程序员的时候,说的可就不是:去实现微前端这样的东西。而是需要落实到怎样去做这样的一件事。

在不同的时期,不同的阶段,我们考虑的架构相关的因素是不同的。按照这个思想,笔者将架构的设计分为了四个层级:

系统级,即应用在整个系统内的关系,如与后台服务如何通讯,与第三方系统如何集成。
应用级,即应用外部的整体架构,如多个应用之间如何共享组件、如何通信等。
模块级,即应用内部的模块架构,如代码的模块化、数据和状态的管理等。
代码级,即从基础设施来保障架构实施。

对应的层级与实施模式,如下图所示:

![前端四个层级]()

系统内架构

在设计前端架构的时候,首先考虑的是应用在整个系统中的位置——它与系统中的其它子系统是怎样的。这种关系包含了架构上的关系、业务上的关系,以及它们之间的协作机制。对于前端应用来说,这一部分的子系统包含了:

  • 其它前端应用。侧重于关注如何与这些应用交互,诸如交互、通讯等。
  • 对接的后台服务。关注于如何与后台服务进行通信,诸如权限、授权、API 管理等。

若是系统间的数据通信,如与后台服务之间的交互,那么只需要规定好数据通信、数据传递的机制即可。这一类的通讯机制,不仅仅包含了前后端分离架构的 API 设计,还包含了各类的数据传递,诸如 OAuth 跳转的 Token 验证。除此,对于传统的多页面应用来说,也需要关注于其中的数据传递,如 Cookie 作为用户凭据等等。

为此,对于前端开发人员来说,关于与后端间的关系,我们所要考虑的主要因素是前后端分离架构的设计。

  • 前后端分离架构。(详见《前端架构:从入门到微前端》)
  • 微前端架构。(详见《前端架构:从入门到微前端》)

而后,我们还需要考虑到前端的客户端展现形式。在大前端的背景之下,它可能是以 PC Web 应用、移动 Web 应用、混合移动应用(结合 Cordova 构架)、混合桌面应用(结合 Electron 框架)、原生移动应用(结合 React Native)等,具体选择何一种技术,取决于我们在之前调查的利益相关者的需求。

当我们做完上述的三个核心的架构决策之后,就需要考虑一下应用的部署架构。不同的客户端形式,或者需要服务端渲染,会在某种程度上影响到前端应用的部署,但是总的影响并不是太大。往往只需要通过反向代理的配置,就可以完成部署的配置。若是与后台服务不在一个域,则需要考虑支持跨域请求或者是后台做一层代码

在有了这些基本的架构设定,便可以往下继续设计下一层级的应用架构。

应用级架构

应用级架构,指的是单个应用与外部应用的关系,如微服务架构下的多个应用的协作。它可以是一个团队下的多个前端应用,又或者是一个组织内的前端应用。其在组织中所处的位置,也进一步决定了我们所需要设计的架构方案。

若是从相关的定义上来看,它与系统级应用存在一定的交集。但是,笔者将其视之为系统级架构的进一步细化。如在系统内架构的层级里,我们定义了微前端架构,而具体的实施细则会放在各个应用中实现的。而诸如应用间的数据如何交换,而不同的应用又有各自不同的实现,则会在这个层级里定义了相应的接口。

与此同时,当我们维护了多个前端应用,必然会去考虑在这些应用之间,复用代码、共享组件库、统一设计等,以减少相应的工作量。为此,在这个层级里,我们会考虑下面的一些架构相关的设计内容:

  • 脚手架。(详见《前端架构:从入门到微前端》)
  • 模式库。(详见《前端架构:从入门到微前端》)
  • 设计系统。(详见《前端架构:从入门到微前端》)

与此同时,在这个层级里,我们决定选择什么前端框架,进行相关的技术选型。

模块级架构

模块级架构,便是深入单个应用内部,更细致的设计应用内部的架构。它所涉及的部分,便是在日常开发中,我们经常接触到的主要部分。我们所做的便是制定一些规范,又或者是更细致的架构设计。这部分的内容,会在我们开始业务编码之前进行设计,在敏捷软件开发中,它称之为 迭代 0/Sprint 0/Iteration 0。相关的内容有:

  • 模块化。(详见《前端架构:从入门到微前端》)
  • 组件化。(详见《前端架构:从入门到微前端》)

除此,对于不同的框架,还涉及到一些框架特定的模式与架构设计,它们会在一定程度上影响单个应用的架构。对于不同的框架来说,所需要涉及的范围都有所不发。如在 Angular 框架中,不需要操心相关的模式,只需要掌握框架定义的规范即可,如使用 Service 来保存应用的状态,使用 Pipe 来处理数据等。而在 React 框架中,则需要设计状态和数据流的管理方式,为此便需要诸如 Flux 或者 Redux 这样的状态管理方案。

代码级:规范与原则

当我们真正编码的时候,考虑的架构因素便是更低层级的内容。这部分的架构设计,便称为代码级的架构设计,它关注于实践过程中的相关规范和原则。这部分的内容相当的多,并且繁琐。它包含了以下的内容,但是又不限于下述的部分:

  • 开发流程。(详见《前端架构:从入门到微前端》)
  • 代码质量及改善。(详见《前端架构:从入门到微前端》)
  • 规范而非默契。(详见《前端架构:从入门到微前端》)

除此,在日常的开发中,还需要注重代码的可维护——简单的代码更容易读性和维护。笔者维护一个 Scala 项目的过程中,便是深有体会——越是写得越抽象的代码,越难以维护。诸如函数式编程是一个好的东西,但是好的东西也容易被烂用,导致人们不喜欢这个东西。

小结

买, 买,买

——节选自《前端架构:从入门到微前端》

查看原文

赞 8 收藏 5 评论 0

phodal 分享了头条 · 2019-07-04

微前端实践

赞 0 收藏 35 评论 0

phodal 发布了文章 · 2019-07-04

微前端如何落地?

在过去的几星期里,随着 Martin Fowler 博客上,那篇 Cam Jackson 写的微前端的文章发布,到处都在讨论 Microfrontend。作为一个微前端 “专家”,我也分享一下:如何去落地微前端。

微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时,它们也可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。

为什么需要微前端?

微前端不是银弹,它和微服务一样会带来大量的挑战。

  • 遗留系统迁移。解决遗留系统,才是人们采用微前端方案最重要的原因
  • 聚合前端应用。微服务架构,可以解耦后端服务间依赖。而微前端,则关注于聚合前端应用。
  • 热闹驱动开发。新的技术,既然很热闹,那么就学吧。

微前端的实现,意味着对前端应用的拆分。拆分应用的目的,并不只是为了架构上好看,还为了提升开发效率。

为此,微前端带来这么一系列的好处:

  1. 应用自治。只需要遵循统一的接口规范或者框架,以便于系统集成到一起,相互之间是不存在依赖关系的。
  2. 单一职责。每个前端应用可以只关注于自己所需要完成的功能。
  3. 技术栈无关。你可以使用 Angular 的同时,又可以使用 React 和 Vue。

除此,它也有一系列的缺点:

  • 应用的拆分基础依赖于基础设施的构建,一旦大量应用依赖于同一基础设施,那么维护变成了一个挑战。
  • 拆分的粒度越小,便意味着架构变得复杂、维护成本变高。
  • 技术栈一旦多样化,便意味着技术栈混乱

毕竟没有银弹。

如何设计微前端架构?

就当前而言,要设计出一个微前端应用不是一件容易的事——还没有最佳实践。在不同的落地案例里,使用的都是不同的方案。出现这种情况的主要原因是,每个项目所面临的情况、所使用的技术都不尽相同。为此,我们需要了解一些基础的微前端模式。

架构模式

微前端应用间的关系来看,分为两种:基座模式(管理式)、自组织式。分别也对应了两者不同的架构模式:

  • 基座模式。通过一个主应用,来管理其它应用。设计难度小,方便实践,但是通用度低。
  • 自组织模式。应用之间是平等的,不存在相互管理的模式。设计难度大,不方便实施,但是通用度高。

就当前而言,基座模式实施起来比较方便,方案上便也是蛮多的。

而不论种方式,都需要提供一个查找应用的机制,在微前端中称为服务的注册表模式。和微服务架构相似,不论是哪种微前端方式,也都需要有一个应用注册表的服务,它可以是一个固定值的配置文件,如 JSON 文件,又或者是一个可动态更新的配置,又或者是一种动态的服务。它主要做这么一些内容:

  • 应用发现。让主应用可以寻找到其它应用。
  • 应用注册。即提供新的微前端应用,向应用注册表注册的功能。
  • 第三方应用注册。即让第三方应用,可以接入到系统中。
  • 访问权限等相关配置。

应用在部署的时候,便可以在注册表服务中注册。如果是基于注册表来管理应用,那么使用基座模式来开发比较方便。

设计理念

在笔者实践微前端的过程中,发现了以下几点是我们在设计的过程中,需要关注的内容:

  • 中心化:应用注册表。这个应用注册表拥有每个应用及对应的入口。在前端领域里,入口的直接表现形式可以是路由,又或者对应的应用映射。
  • 标识化应用。 我们需要一个标识符来标识不同的应用,以便于在安装、卸载的时候,能寻找到指定的应用。一个简单的模式,就是通过康威定律来命名应用。
  • 应用生命周期管理。
  • 高内聚,低耦合。

生命周期

前端微架构与后端微架构的最大不同之处,也在于此——生命周期。微前端应用作为一个客户端应用,每个应用都拥有自己的生命周期:

  • Load,决定加载哪个应用,并绑定生命周期
  • bootstrap,获取静态资源
  • Mount,安装应用,如创建 DOM 节点
  • Unload,删除应用的生命周期
  • Unmount,卸载应用,如删除 DOM 节点、取消事件绑定

这部分的内容事实上,也就是微前端的一个难点所在,如何以合适的方式来加载应用——毕竟每个前端框架都各自不同,其所需要的加载方式也是不同的。当我们决定支持多个框架的时候,便需要在这一部分进入更细致的研究。

如何拆分?

随后,我们要面临的一个挑战是:如何去拆分应用。

技术方式

从技术实践上,微前端架构可以采用以下的几种方式进行:

  • 路由分发式。通过 HTTP 服务器的反向代理功能,来将请求路由到对应的应用上。
  • 前端微服务化。在不同的框架之上设计通讯、加载机制,以在一个页面内加载对应的应用。
  • 微应用。通过软件工程的方式,在部署构建环境中,组合多个独立应用成一个单体应用。
  • 微件化。开发一个新的构建系统,将部分业务功能构建成一个独立的 chunk 代码,使用时只需要远程加载即可。
  • 前端容器化。通过将 iFrame 作为容器,来容纳其它前端应用。
  • 应用组件化。借助于 Web Components 技术,来构建跨框架的前端应用。

实施的方式虽然多,但是都是依据场景而采用的。有些场景下,可能没有合适的方式;有些场景下,则可以同时使用多种方案。

路由分发式

路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。如下图所示:

路由分发式

就当前而言,路由分发式的架构应该是采用最多、最容易的 “微前端” 方案。但是

前端微服务化

前端微服务化,是微服务架构在前端的实施,每个前端应用都是完全独立(技术栈、开发、部署、构建独立)、自主运行的,最后通过模块化的方式组合出完整的前端应用。其架构如下图所示:

路由分发式

采用这种方式意味着,一个页面上同时存在二个及以上的前端应用在运行。而路由分发式方案,则是一个页面只有唯一一个应用。

组合式集成:微应用化

微应用化,即在开发时,应用都是以单一、微小应用的形式存在,而在运行时,则通过构建系统合并这些应用,组合成一个新的应用。其架构如下图所示:

路由分发式

微应用化更多的是以软件工程的方式,来完成前端应用的开发,因此又可以称之为组合式集成。对于一个大型的前端应用来说,采用的架构方式,往往会是通过业务作为主目录,而后在业务目录中放置相关的组件,同时拥有一些通用的共享模板。

微件化

微件(widget),指的是一段可以直接嵌入在应用上运行的代码,它由开发人员预先编译好,在加载时不需要再做任何修改或者编译。而微前端下的微件化则指的是,每个业务团队编写自己的业务代码,并将编译好的代码部署(上传或者放置)到指定的服务器上,在运行时,我们只需要加载相应的业务模块即可。对应的,在更新代码的时候,我们只需要更新对应的模块即可。下图便是微件化的架构示意图:

路由分发式

在非单面应用时代,要实现微件化方案,是一件特别容易的事。从远程加载来对应的 JavaScript 代码,在浏览器上执行,生成对应的组件嵌入到页面的相应部分。对于业务组件也是类似的,提前编写好我们的业务组件,当需要对应的组件时再响应、执行。

前端容器化

前端容器:iframe

iframe 作为一个非常 “古老” 的,人人都觉得普通的技术,却一直很管用。它能有效地将另一个网页/单页面应用嵌入到当前页面中,两个页面间的 CSS 和 JavaScript 是相互隔离的——除去 iframe 父子通信部分的代码,它们之间的代码是完全不相互干扰的。iframe 便相当于是创建了一个全新的独立的宿主环境,类似于沙箱隔离,它意味着前端应用之间可以相互独立运行。

结合 Web Components 构建

Web Components 是一套不同的技术,允许开发者创建可重用的定制元素(它们的功能封装在代码之外),并且在 web 应用中使用它们。

路由分发式

目前困扰 Web Components 技术推广的主要因素,在于浏览器的支持程度。在 Chrome 和 Opera 浏览器上,对于 Web Components 支持良好,而对于 Safari、IE、Firefox 浏览器的支持程度,并没有那么理想。

业务拆分

与微服务类似,要划分不同的前端边界,不是一件容易的事。就当前而言,以下几种方式是常见的划分微前端的方式:

  • 按照业务拆分
  • 按照权限拆分
  • 按照变更的频率拆分
  • 按照组织结构拆分。利用康威定律来进一步拆分前端应用。
  • 跟随后端微服务划分。实践证明, DDD 与事件风暴是一种颇为有效的后端微前端拆分模式,对于前端来说,它也颇有有效——直接跟踪后端服务。

每个项目都有自己特殊的背景,切分微前端的方式便不一样。即使项目的类型相似,也存在一些细微的差异。

微前端之外

如果微前端对于你们来说困境重重,还有一些不错的架构模式可以采用。

应用微化架构

应用微化架构,是一种开发时整体,构建时拆分,运行时分离的前端架构模式。即应用微化架构从一份代码中,构建出适用于不同环境的多套目标代码。实现上它是一种:

  • 构建时拆分架构
  • 代码删除架构。笑,以删代码的方式,来形成每个前端应用。
  • 微前端准备式架构。即,随时可以拆分为多个前端应用。

由于它与微应用化的相似性,我们将它与微应用化做一个对比。它与微应用化不同的是,应用微化是在构建时对应用进行拆分,而非在本地模式下对应用拆分。相似的是,它也是基于构建系统的应用拆分方式。

可拆分式微前端

即:微应用化,是一个随时可合并式架构。而应用微化,则是一个随时可拆分式架构

它不仅仅是一个适合于前端的架构模式,也是一适用于后端的架构模式

整洁前端架构

Clean Architecture 是由 Robert C. Martin 在 2012 年提出的架构模式。它具有这么一些特点:框架无关性、可被测试、UI 无关性、数据库无关性、外部机构(agency)无关性。

对于前端架构来说,Clean Architecure 实际上是:Clean Architecture + MVP + 组件化。如下图所示:

考虑到应用的规模,这里以 Angular + TypeScript 作为示例:

Clean Frontend

这种架构模式特别适合于:组织内即写前端又同后端的团队。它易于映射前后端 API,且可以使用 usecase 作为防腐层。

没有银弹。不得不提及的是,对于小规模的团队来说,它带来的弊端可能会远远大于好处——带来大量冗余的代码。尽管通过 Angular Schematics 可以通过参数来生成代码,但是这种分层架构地于简单的应用来说,还是过于复杂、难以上手。对于不写测试的项目来说 ,usecase 也不能带来它们所承诺的好处。

结论

微前端不是银弹,当前也没有最佳实践,但是这是一个非常好的学习机会。


节选自《前端架构:从入门到微前端》

查看原文

赞 47 收藏 35 评论 1

phodal 分享了头条 · 2019-04-08

中台之后,便无代码。

赞 1 收藏 12 评论 0

phodal 发布了文章 · 2019-04-08

无代码编程

中台之后,便无代码。

规模化的组织,经常要面临这样的挑战:每个应用的基础设施是相同的,部分的代码也是相同的,甚至于它们可能只是数据模型不同而已。结果却导致了,他/她们要一次又一次地重新编写一个应用。

对于一个新的应用而言,它需要对接大量的三方(非自己团队)服务。服务之间的不断变化 ,导致了对应的使用方也需要发生变化。不断变化的业务,导致了前台的设计不断变化。为了应对快速谈的的前台服务,后台便诞生了中台,以提供快速的响应能力。而随着中台进一步沉淀,从某种形式上趋于稳定,而前台仍然需要快速地响应能力。

于是乎,作为一个前端开发人员,我们不断提炼和复用代码,想着的模式在上一篇文章 减少代码量的 7~8 种方式 中提到了:

  • 脚手架
  • 组件库
  • 模式库
  • 模板和模板应用

对应的,我们还创建了一系列的 CLI、工具集、编程器插件以及设计系统,以完成整个系统的快速开发。然而,我们还缺少一套有效的工具,来统一化的管理这些工具。

换句话来说,就是:我们需要一个前端的中台,它便是无代码/低代码编程。

什么是无代码编程?

无代码/低代码是一种创建应用的方法,它可以让开发人员使用最少的编码知识,来快速开发应用程序。它可以在图形界面中,使用可视化建模的方式,来组装和配置应用程序。开发人员可以直接跳过所有的基础架构,只关注于使用代码来实现业务逻辑。

当然,从开发人员的角度来看,降低代码量,可能是:

  1. 框架本身处理了复杂性。毕竟 “复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。”
  2. 代码生成减少了工作量。大量的复制、粘贴需要更多的时间。

流程

只是凭借这个概念,我们是无法理解无代码编程的。于是,我画了一张图来展示相应的架构和流程:

在这里插入图片描述

依照我的观点来看,我将无代码编程分为了两部分:

  • 用于构建 UI 的编辑器——一种在线的拖拽式 UI 设计和页面构建工具
  • 用于编写业务逻辑的流编辑器——通过流编程的方式来编写业务代码(多数是对于数据的处理)

UI 编程器。为了设计出我们的 UI 构建器,我们需要准备好一系列的基础设施:

  • UI 编程器。用于拖拽式设计 UI。
  • 空白脚手架。一个带有完整的应用生命周期的项目,但是它是一个空白的项目——用于我们在构建 UI 的过程中,随时随地的添加组件和代码。
  • 设计系统。我们需要一个完整的组件库,大量的页面模板,以及一定数量的模板应用,减少相应的开发工具量。
  • 代码片段集。它将设计系统中的组件库进一步实例化成代码段,在完成编辑后通过 CLI 来动态编辑代码。
  • DSL(领域特定语言,可选)。中间生成物,用于隔离框架与设计。

流编程器。随后,为了在

  • 流编程器。用于拖拽式、输入编写业务代码。
  • 后端服务。如果不能提供现成的后端服务,则需要拥有一个标准的 API 规范,以及相应的 mock server。
  • 模式库。包含相应的业务处理代码,如通用的登录、数据获取、UI 交互等。
  • DSL(领域特定语言,可选)。同上

当然了,我们还需要能实时预览构建出来的应用。随后,我们执行了构建,而后构建出了一个半成品应用。开发人员只需要在它的基础上开发应用即可。而在开发人员开发的过程中,我们可以设计一系列的工具,来帮助开发人员更快速地构建应用。

  • 编辑器插件。包含设计系统、模式库等的自动完成代码,以及组织内部常用的代码库。
  • 调试工具。对于混合类型的应用而言,我们还需要一个开发工具来快速构建应用。

从上述的流程上来看,无代码编程还具有以下的特点:

  • 拖放式界面。又或者是可视化模型——基于节点和箭头
  • 基于视觉的设计。
  • 可扩展的设计。如对于插件、插件商店,社区等一系列的支持。
  • 跨平台功能。支持 PC Web 应用开发,支持移动应用构架等。
  • 强大的部署后。即平台包含着整个应用的生命周期。
  • 拥有丰富的集成支持。可以随意的找到需要的组件,以及对应的后台服务。
  • 配置化。它也意味着大量的自定义配置。
  • 自制的领域特定语言(可选)。用于构建时优化。

优缺点

相应的,它具有以下的一些特点:

  1. 高效。不用多说,节省时间和开发成本。
  2. 有限的 Bug,安全性。
  3. 低成本。其所需的预算非常有限。
  4. 易用(取决于设计)。
  5. 开发速度更快。
  6. 开发过程中的 AI 。
  7. 维护成本低。

对应的相应的缺点有:

  1. 仍然需要编程技能。
  2. 受限的自定义能力。
  3. 可扩展性成了新的问题。
  4. 集成受限。

就当前而言,低代码开发平台通常分为两大类:

  • 对于外部:制作简单的产品,如网络移动应用程序
  • 对于内部:为您的团队或企业创建业务应用程序

诸如只使用 CRUD、表单、验证、简单聚合、分页等简易的服务。最常见的例子就是表单构建了,诸如金数据这样的应用,便是可以直接通过拖拽元素来生成,相应的开源实现有 jQuery Form Builder。对于开发人员来说,我们只需要定义好数据模型,再通过拖拽来决定元素的位置即可。从这种角度来看,只要能使用 Serverless 构建的应用和服务,都可以直接使用低代码开发模式。

开发流程对比

从我们的理解来看,传统应用的开发流程是:

  1. 分析、设计、确认、规划需求
  2. 设计系统架构
  3. 搭建前后端项目。选择技术栈、从零开始搭建或者从脚手架中创建。
  4. 搭建持续集成。
  5. 创建线框图和高保真原型。
  6. 设计数据模型,定义前后端契约,即 API URI、方法、字段等。
  7. 前后端实现业务逻辑。
  8. 前端实现 UI 页面。
  9. 集成第三方后端服务。
  10. 功能需求测试(DEV、QA、ST、UAT)
  11. 跨功能需求测试(安全性、性能等)
  12. 部署到生产环境。

而,低代码开发流程:

  1. 分析、设计、确认、规划需求
  2. 选择需要的第三方 API
  3. 在可视 IDE 中绘制应用程序的工作流程、数据模型和用户界面。
  4. 连接 API——通常使用服务、函数发现。
  5. 编写业务逻辑(可选)。手动代码添加到前端或者自定义自动生成的 SQL 查询。
  6. 用户验收测试。
  7. 部署到生产环境。

从步骤上来看,无代码编程少了几个步骤。这些步骤都因为大量丰富的内部系统集成,而变得非常简单。

适用场景

就当前而言,无代码编程实际上是一种高度的场景预设的模式。因此,它存在一定的适用场景:

  • 模型驱动开发
  • 快速 UI 构建
  • 极简的业务功能。使用这样的工具,也意味着,我们对于交互和可
  • IT 资源受限。在资源受限的情况下,能快速开发出符合业务需求的应用最重要。

而从流程上来看,对于一部分的应用来说,我们并不能实现无代码编程——存在一些业务上的不同之处。因此,多数场景之下,只是实现了低代码编程

若是想真实的无代码编程,则需要一些更特定的场景:

  • 设计表格(输入数据)
  • 创建报告(组织数据)
  • 常规调度和自动化过程(操纵数据)

更多的场景正在探索中。

无代码编程的挑战

无代码编程,除了需要准备好上述的一系列基础设施,还不可避免地会遇到一系列挑战。

  • 谁来写这部分代码?
  • 客户端的基础设施准备。
  • 服务端的服务平台搭建。
  • 统一用户体验设计。设计出一系列能便利组合的组件,及对应的模板页面。与此同时,它们还能适应于不同的风格,即有多样性的主题支持。
  • DevOps 流水线设计。低代码编程,依赖于一系列的自动化工具,以实现构建、调试、部署以及维护,同时还包含应用的测试。
  • 领域语言设计。
  • 自动化测试。如果我们的前端代码是自动生成的,那么我们还需要对它们进行测试吗?这是一个好问题,而如果代码是自动生成的,那么测试也应该是自动生成的。毕竟要在平台上,编写大量的自动化测试,以保证平台的质量。

其中,有一些部分略微复杂一些,我们大概可以探索一下。

谁来写这部分代码?

在我们创建这样一个平台和工具时,我们要考虑的第一个问题是,我们这个工具是为谁写的?

  • 没有编程经验的人。如业务人员,他/她们对于业务系统有着非常丰富的经验。这也意味着,我们
  • 有编程知识,但是没有经验的人。
  • 有一定经验的开发人员。
  • 有丰富经验的开发人员。对于专业的人来说,自动化就意味着缺少灵活度。甚至与自己编写相比,他/她们要花费更多的时间来修复生成的代码。

显然,对于相当有经验的开发人员而言,这个工具并不一定是他/她们所需要的。

客户端基础设施

从我的理解来看,它适合于 快速的 MVP 构建,并且生成的代码还应该方便修改,而不是诸如早期的 DreamWeaver 或者 FrontPage 这样的工具。

而与此同时,由于面向的开发人员水平不同,我们所需要做的工具也不同:

  1. 支持云构建和调试。
  2. GUI 编程应用。
  3. 代码生成。
  4. 设计系统体系构建。组件库搭建,模板应用创建等。

更难的是,容易让开发人员能接受代码生成。

服务端

对于一个低代码平台而言,它对应的后端应该:

  1. 大量可用地现有服务。身份验证、安全性、推送能力、地图等等
  2. 快速构建出后端服务。若是有内部 Serverless 或者 FaaS 方案,可以说是再好不过了。
  3. 方便与第三方服务集成。
  4. 灵活性。支持多语言等。

统一的后端服务 API,对于后端服务来说,我们需要一个通用的范式。所有的 API 应该按照这样的范式来设计。不过,作为一个 API 的消费方,我们可能没有这么大的权力,但是我们可以采用装饰器模式,即封装第三方 API 成统一的方式。为此,我们采用的方式,仍然是:

  • 契约。诸如 Swagger UI,它可以直接创建一个简易可用的服务。
  • BFF。即我们一一去按我们的需要,去封装这些第三方应用。
  • 查询语言。与自己编写 BFF 相比,更简单的方式是采用:GraphQL 这样的后端查询语言,便捷性更高、模式更加灵活。

在开发前的设计期里,我们需要首先设计出对应的领域模型。

领域语言设计

低代码环境使用(图形)建模语言来指定整个系统、产品的行为。它意味着:

  1. 将数据结构、领域模式应用到程序的各个层级中。
  2. 将业务规则、决策融入到应用中(层级)。

这也就意味着,我们需要设计一个模型语言。而它对于我们而言,实际上是领域特定语言(DSL)。如下是一个简单的 DSL 示例,用于描述使用到的组件:

  {
    'style': '',
    'id': 2,
    'blocks': [
      {
        'content': {
          'content': 'content',
          'title': 'hello'
        },
        'type': 'card'
      }
    ]
  }

除此,我们还需要设计对应的布局 DSL,诸如于:

H:[circle1(circle1.height)] // set aspect-ratio for circle1
HV:[circle2..5(circle1)]    // use same width/height for other circles
H:|[circle1]-[circle2]-[circle3]-[circle4]-[circle5]|
V:|~[circle1..5]~|          // center all circles vertically

最后,我们还需要将流代码,转换为真实的项目代码。三种类型的 DSL 结合下来,都不是一个轻松的工具。

原型设计

写好现有的组件,通用型接口。如常见的登录接口,。对于使用登录接口的业务来说,它们只关心三部分的内容:

  1. 成功登录。
  2. 取消登录。
  3. 登录失败。对于客户端而言,可以视为取消登录。对于服务端来说,则可能是密码错误、用户名不存在、账号被锁定等。

对应于以上情景,又有一些通用的逻辑处理:

  1. 登录成功。保存 Token,并返回历史页面。
  2. 登录失败。弹出一个自定义内容的提示框。

这些代码是相似的。

前端原型

在一些简单的前端应用里:

  • 模板。只是在使用这些模板,再为这些模板设置相应的属性,绑定对应的值。
  • 数据。其过程都只是在各种保存变量的值,并 CRUD 这些变量的路上。为此,我们需要一个数据处理的管道架构设计,用于处理这些值。
  • 函数。事实上,我们的所有函数都只是一些管理函数,只用于处理这些对应的逻辑。

这些常见的功能都可以做成一些组件,它们对于某些应用来说,代码相应的重复。

  • 无限加载页面。只需要绑定上 API,再控制一下元素的显示与隐藏即可。
  • 表单。定义好字段即类型,对应的前后台逻辑都有了。除此,我们还需要为它们自定义好常见的规则,如正则表达式。而一旦表单之间有联动,那么这个组件的设计就更加麻烦了。
  • 卡片式元素。
  • 表单和表格展示。
  • 常见图表。事实上,已经有一系列的图表工具了,我们只是在它们在基础上,进行了二次封装而已——使得它们可以变成领域语言的形式。
  • 高级的响应式布局。与每个应用独立开发布局不同的是,低代码平台需要提供一套强大的自定义、响应式布局设计——即要满足移动端的通用模式,还要满足桌面版的通用模式。如对于大量数据来说,桌面端使用的是 Pagination,移动端使用的是无限滚动。

后端原型

事实上,对于后端来说,低成本平台意味着,代码生成及服务生成。而服务本身是有限的,既然是业务上发生了一些变化,后端服务也可能并不会发生变化。

它也意味着:

  • 微服务化。每个后端服务要尽可能的小。
  • API 规范化。即采用统一的 API 格式,接受统一的权限管理
  • 大量的 API 服务。
  • 快速集成第三方服务方案。集成第三方服务是开发应用不可避免的情况。为了应对这个问题,我们需要做准备好对应的创建服务逻辑,传入第三方服务所需要的参数,便可以直接生成我们的转发服务。

那么,问题来了,既然如此,我们是否能提供一个定制的工具呢?让每个人可以创建自己的组件流?

答案,显然是可以的。

概念证明

于是乎,在我最近设计的 PoC (概念证明)里,采用的是 Anguar 框架。相应的基本思想如下:

  1. 使用 Material Design 作为组件库,使用 CDK 的 Drag 来实现拖拽功能。每个拖拽的组件,称为 element(元素),由 ElementDispatcher 由根据数据生成对应的组件。可拖拽的部分由两部分组成:布局 + 元素
  2. UI 构建完后,生成对应的 DSL,目前采用的是 JSON。毕竟数据结构是最简单的领域特定语言。
  3. 借由 Angular Schematics 解析这部分的 DSL,来生成相应的项目代码。

其它

相关开源项目:

参考资料:

  1. https://www.process.st/low-code/
  2. https://medium.com/@stefan.dreverman/decisions-to-take-for-your-low-code-architecture-c0768b606f
  3. https://medium.com/@stefan.dreverman/analyzing-coinmarketcap-data-with-neo4j-4930ba0068e1
  4. https://www.outsystems.com/blog/what-is-low-code.html
  5. https://medium.com/@stefan.dreverman/starting-a-low-code-application-architecture-13170fcd6fc7
  6. https://www.quora.com/What-is-low-code
  7. 无代码编程
查看原文

赞 21 收藏 12 评论 1

phodal 评论了文章 · 2018-07-17

实施微前端的六种方式

微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用

由此带来的变化是,这些前端应用可以独立运行独立开发独立部署。以及,它们应该可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。

注意:这里的前端应用指的是前后端分离的单应用页面,在这基础才谈论微前端才有意义。

结合我最近半年在微前端方面的实践和研究来看,微前端架构一般可以由以下几种方式进行:

  1. 使用 HTTP 服务器的路由来重定向多个应用
  2. 在不同的框架之上设计通讯、加载机制,诸如 MooaSingle-SPA
  3. 通过组合多个独立应用、组件来构建一个单体应用
  4. iFrame。使用 iFrame 及自定义消息传递机制
  5. 使用纯 Web Components 构建应用
  6. 结合 Web Components 构建

不同的方式适用于不同的使用场景,当然也可以组合一起使用。那么,就让我们来一一了解一下,为以后的架构演进做一些技术铺垫。

基础铺垫:应用分发路由 -> 路由分发应用

在一个单体前端、单体后端应用中,有一个典型的特征,即路由是由框架来分发的,框架将路由指定到对应的组件或者内部服务中。微服务在这个过程中做的事情是,将调用由函数调用变成了远程调用,诸如远程 HTTP 调用。而微前端呢,也是类似的,它是将应用内的组件调用变成了更细粒度的应用间组件调用,即原先我们只是将路由分发到应用的组件执行,现在则需要根据路由来找到对应的应用,再由应用分发到对应的组件上。

后端:函数调用 -> 远程调用

在大多数的 CRUD 类型的 Web 应用中,也都存在一些极为相似的模式,即:首页 -> 列表 -> 详情:

  • 首页,用于面向用户展示特定的数据或页面。这些数据通常是有限个数的,并且是多种模型的。
  • 列表,即数据模型的聚合,其典型特点是某一类数据的集合,可以看到尽可能多的数据概要(如 Google 只返回 100 页),典型见 Google、淘宝、京东的搜索结果页。
  • 详情,展示一个数据的尽可能多的内容。

如下是一个 Spring 框架,用于返回首页的示例:

@RequestMapping(value="/")
public ModelAndView homePage(){
   return new ModelAndView("/WEB-INF/jsp/index.jsp");
}

对于某个详情页面来说,它可能是这样的:

@RequestMapping(value="/detail/{detailId}")
public ModelAndView detail(HttpServletRequest request, ModelMap model){
   ....
   return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail);
}

那么,在微服务的情况下,它则会变成这样子:

@RequestMapping("/name")
public String name(){
    String name = restTemplate.getForObject("http://account/name", String.class);
    return Name" + name;
}

而后端在这个过程中,多了一个服务发现的服务,来管理不同微服务的关系。

前端:组件调用 -> 应用调用

在形式上来说,单体前端框架的路由和单体后端应用,并没有太大的区别:依据不同的路由,来返回不同页面的模板。

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
  { path: 'detail/:id', component: DetailComponent },
];

而当我们将之微服务化后,则可能变成应用 A 的路由:

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
];

外加之应用 B 的路由:

const appRoutes: Routes = [
  { path: 'detail/:id', component: DetailComponent },
];

而问题的关键就在于:怎么将路由分发到这些不同的应用中去。与此同时,还要负责管理不同的前端应用。

路由分发式微前端

路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。

就当前而言,通过路由分发式的微前端架构应该是采用最多、最易采用的 “微前端” 方案。但是这种方式看上去更像是多个前端应用的聚合,即我们只是将这些不同的前端应用拼凑到一起,使他们看起来像是一个完整的整体。但是它们并不是,每次用户从 A 应用到 B 应用的时候,往往需要刷新一下页面。

在几年前的一个项目里,我们当时正在进行遗留系统重写。我们制定了一个迁移计划:

  1. 首先,使用静态网站生成动态生成首页
  2. 其次,使用 React 计划栈重构详情页
  3. 最后,替换搜索结果页

整个系统并不是一次性迁移过去,而是一步步往下进行。因此在完成不同的步骤时,我们就需要上线这个功能,于是就需要使用 Nginx 来进行路由分发。

如下是一个基于路由分发的 Nginx 配置示例:

http {
  server {
    listen       80;
    server_name  www.phodal.com;
    location /api/ {
      proxy_pass http://http://172.31.25.15:8000/api;
    }
    location /web/admin {
      proxy_pass http://172.31.25.29/web/admin;
    }
    location /web/notifications {
      proxy_pass http://172.31.25.27/web/notifications;
    }
    location / {
      proxy_pass /;
    }
  }
}

在这个示例里,不同的页面的请求被分发到不同的服务器上。

随后,我们在别的项目上也使用了类似的方式,其主要原因是:跨团队的协作。当团队达到一定规模的时候,我们不得不面对这个问题。除此,还有 Angluar 跳崖式升级的问题。于是,在这种情况下,用户前台使用 Angular 重写,后台继续使用 Angular.js 等保持再有的技术栈。在不同的场景下,都有一些相似的技术决策。

因此在这种情况下,它适用于以下场景:

  • 不同技术栈之间差异比较大,难以兼容、迁移、改造
  • 项目不想花费大量的时间在这个系统的改造上
  • 现有的系统在未来将会被取代
  • 系统功能已经很完善,基本不会有新需求

而在满足上面场景的情况下,如果为了更好的用户体验,还可以采用 iframe 的方式来解决。

使用 iFrame 创建容器

iFrame 作为一个非常古老的,人人都觉得普通的技术,却一直很管用。

HTML 内联框架元素<iframe> 表示嵌套的正在浏览的上下文,能有效地将另一个 HTML 页面嵌入到当前页面中。

iframe 可以创建一个全新的独立的宿主环境,这意味着我们的前端应用之间可以相互独立运行。采用 iframe 有几个重要的前提:

  • 网站不需要 SEO 支持
  • 拥有相应的应用管理机制

如果我们做的是一个应用平台,会在我们的系统中集成第三方系统,或者多个不同部门团队下的系统,显然这是一个不错的方案。一些典型的场景,如传统的 Desktop 应用迁移到 Web 应用:

Angular Tabs 示例

如果这一类应用过于复杂,那么它必然是要进行微服务化的拆分。因此,在采用 iframe 的时候,我们需要做这么两件事:

  • 设计管理应用机制
  • 设计应用通讯机制

加载机制。在什么情况下,我们会去加载、卸载这些应用;在这个过程中,采用怎样的动画过渡,让用户看起来更加自然。

通讯机制。直接在每个应用中创建 postMessage 事件并监听,并不是一个友好的事情。其本身对于应用的侵入性太强,因此通过 iframeEl.contentWindow 去获取 iFrame 元素的 Window 对象是一个更简化的做法。随后,就需要定义一套通讯规范:事件名采用什么格式、什么时候开始监听事件等等。

有兴趣的读者,可以看看笔者之前写的微前端框架:Mooa

不管怎样,iframe 对于我们今年的 KPI 怕是带不来一丝的好处,那么我们就去造个轮子吧。

自制框架兼容应用

不论是基于 Web Components 的 Angular,或者是 VirtualDOM 的 React 等,现有的前端框架都离不开基本的 HTML 元素 DOM。

那么,我们只需要:

  1. 在页面合适的地方引入或者创建 DOM
  2. 用户操作时,加载对应的应用(触发应用的启动),并能卸载应用。

第一个问题,创建 DOM 是一个容易解决的问题。而第二个问题,则一点儿不容易,特别是移除 DOM 和相应应用的监听。当我们拥有一个不同的技术栈时,我们就需要有针对性设计出一套这样的逻辑。

尽管 Single-SPA 已经拥有了大部分框架(如 React、Angular、Vue 等框架)的启动和卸载处理,但是它仍然不是适合于生产用途。当我基于 Single-SPA 为 Angular 框架设计一个微前端架构的应用时,我最后选择重写一个自己的框架,即 Mooa

虽然,这种方式的上手难度相对比较高,但是后期订制及可维护性比较方便。在不考虑每次加载应用带来的用户体验问题,其唯一存在的风险可能是:第三方库不兼容

但是,不论怎样,与 iFrame 相比,其在技术上更具有可吹牛逼性,更有看点。同样的,与 iframe 类似,我们仍然面对着一系列的不大不小的问题:

  • 需要设计一套管理应用的机制。
  • 对于流量大的 toC 应用来说,会在首次加载的时候,会多出大量的请求

而我们即又要拆分应用,又想 blabla……,我们还能怎么做?

组合式集成:将应用微件化

组合式集成,即通过软件工程的方式在构建前、构建时、构建后等步骤中,对应用进行一步的拆分,并重新组合。

从这种定义上来看,它可能算不上并不是一种微前端——它可以满足了微前端的三个要素,即:独立运行独立开发独立部署。但是,配合上前端框架的组件 Lazyload 功能——即在需要的时候,才加载对应的业务组件或应用,它看上去就是一个微前端应用。

与此同时,由于所有的依赖、Pollyfill 已经尽可能地在首次加载了,CSS 样式也不需要重复加载。

常见的方式有:

  • 独立构建组件和应用,生成 chunk 文件,构建后再归类生成的 chunk 文件。(这种方式更类似于微服务,但是成本更高)
  • 开发时独立开发组件或应用,集成时合并组件和应用,最后生成单体的应用。
  • 在运行时,加载应用的 Runtime,随后加载对应的应用代码和模板。

应用间的关系如下图所示(其忽略图中的 “前端微服务化”):

组合式集成对比

这种方式看上去相当的理想,即能满足多个团队并行开发,又能构建出适合的交付物。

但是,首先它有一个严重的限制:必须使用同一个框架。对于多数团队来说,这并不是问题。采用微服务的团队里,也不会因为微服务这一个前端,来使用不同的语言和技术来开发。当然了,如果要使用别的框架,也不是问题,我们只需要结合上一步中的自制框架兼容应用就可以满足我们的需求。

其次,采用这种方式还有一个限制,那就是:规范!规范!规范!。在采用这种方案时,我们需要:

  • 统一依赖。统一这些依赖的版本,引入新的依赖时都需要一一加入。
  • 规范应用的组件及路由。避免不同的应用之间,因为这些组件名称发生冲突。
  • 构建复杂。在有些方案里,我们需要修改构建系统,有些方案里则需要复杂的架构脚本。
  • 共享通用代码。这显然是一个要经常面对的问题。
  • 制定代码规范。

因此,这种方式看起来更像是一个软件工程问题。

现在,我们已经有了四种方案,每个方案都有自己的利弊。显然,结合起来会是一种更理想的做法。

考虑到现有及常用的技术的局限性问题,让我们再次将目光放得长远一些。

纯 Web Components 技术构建

在学习 Web Components 开发微前端架构的过程中,我尝试去写了我自己的 Web Components 框架:oan。在添加了一些基本的 Web 前端框架的功能之后,我发现这项技术特别适合于作为微前端的基石

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 Web 应用中使用它们。

它主要由四项技术组件:

  • Custom elements,允许开发者创建自定义的元素,诸如 <today-news></today-news>。
  • Shadow DOM,即影子 DOM,通常是将 Shadow DOM 附加到主文档 DOM 中,并可以控制其关联的功能。而这个 Shadow DOM 则是不能直接用其它主文档 DOM 来控制的。
  • HTML templates,即 <template><slot> 元素,用于编写不在页面中显示的标记模板。
  • HTML Imports,用于引入自定义组件。

每个组件由 link 标签引入:

<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">

随后,在各自的 HTML 文件里,创建相应的组件元素,编写相应的组件逻辑。一个典型的 Web Components 应用架构如下图所示:

Web Components 架构

可以看到这边方式与我们上面使用 iframe 的方式很相似,组件拥有自己独立的 ScriptsStyles,以及对应的用于单独部署组件的域名。然而它并没有想象中的那么美好,要直接使用 Web Components 来构建前端应用的难度有:

  • 重写现有的前端应用。是的,现在我们需要完成使用 Web Components 来完成整个系统的功能。
  • 上下游生态系统不完善。缺乏相应的一些第三方控件支持,这也是为什么 jQuery 相当流行的原因。
  • 系统架构复杂。当应用被拆分为一个又一个的组件时,组件间的通讯就成了一个特别大的麻烦。

Web Components 中的 ShadowDOM 更像是新一代的前端 DOM 容器。而遗憾的是并不是所有的浏览器,都可以完全支持 Web Components。

结合 Web Components 构建

Web Components 离现在的我们太远,可是结合 Web Components 来构建前端应用,则更是一种面向未来演进的架构。或者说在未来的时候,我们可以开始采用这种方式来构建我们的应用。好在,已经有框架在打造这种可能性。

就当前而言,有两种方式可以结合 Web Components 来构建微前端应用:

  • 使用 Web Components 构建独立于框架的组件,随后在对应的框架中引入这些组件
  • 在 Web Components 中引入现有的框架,类似于 iframe 的形式

前者是一种组件式的方式,或者则像是在迁移未来的 “遗留系统” 到未来的架构上。

在 Web Components 中集成现有框架

现有的 Web 框架已经有一些可以支持 Web Components 的形式,诸如 Angular 支持的 createCustomElement,就可以实现一个 Web Components 形式的组件:

platformBrowser()
    .bootstrapModuleFactory(MyPopupModuleNgFactory)
        .then(({injector}) => {
            const MyPopupElement = createCustomElement(MyPopup, {injector});
            customElements.define(‘my-popup’, MyPopupElement);
});

在未来,将有更多的框架可以使用类似这样的形式,集成到 Web Components 应用中。

集成在现有框架中的 Web Components

另外一种方式,则是类似于 Stencil 的形式,将组件直接构建成 Web Components 形式的组件,随后在对应的诸如,如 React 或者 Angular 中直接引用。

如下是一个在 React 中引用 Stencil 生成的 Web Components 的例子:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import 'test-components/testcomponents';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

在这种情况之下,我们就可以构建出独立于框架的组件。

同样的 Stencil 仍然也只是支持最近的一些浏览器,比如:Chrome、Safari、Firefox、Edge 和 IE11

复合型

复合型,对就是上面的几个类别中,随便挑几种组合到一起。

我就不废话了~~。

结论

那么,我们应该用哪种微前端方案呢?答案见下一篇《微前端快速选型指南》

相关资料:

查看原文

phodal 发布了文章 · 2018-07-08

实施微前端的六种方式

微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用

由此带来的变化是,这些前端应用可以独立运行独立开发独立部署。以及,它们应该可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。

注意:这里的前端应用指的是前后端分离的单应用页面,在这基础才谈论微前端才有意义。

结合我最近半年在微前端方面的实践和研究来看,微前端架构一般可以由以下几种方式进行:

  1. 使用 HTTP 服务器的路由来重定向多个应用
  2. 在不同的框架之上设计通讯、加载机制,诸如 MooaSingle-SPA
  3. 通过组合多个独立应用、组件来构建一个单体应用
  4. iFrame。使用 iFrame 及自定义消息传递机制
  5. 使用纯 Web Components 构建应用
  6. 结合 Web Components 构建

不同的方式适用于不同的使用场景,当然也可以组合一起使用。那么,就让我们来一一了解一下,为以后的架构演进做一些技术铺垫。

基础铺垫:应用分发路由 -> 路由分发应用

在一个单体前端、单体后端应用中,有一个典型的特征,即路由是由框架来分发的,框架将路由指定到对应的组件或者内部服务中。微服务在这个过程中做的事情是,将调用由函数调用变成了远程调用,诸如远程 HTTP 调用。而微前端呢,也是类似的,它是将应用内的组件调用变成了更细粒度的应用间组件调用,即原先我们只是将路由分发到应用的组件执行,现在则需要根据路由来找到对应的应用,再由应用分发到对应的组件上。

后端:函数调用 -> 远程调用

在大多数的 CRUD 类型的 Web 应用中,也都存在一些极为相似的模式,即:首页 -> 列表 -> 详情:

  • 首页,用于面向用户展示特定的数据或页面。这些数据通常是有限个数的,并且是多种模型的。
  • 列表,即数据模型的聚合,其典型特点是某一类数据的集合,可以看到尽可能多的数据概要(如 Google 只返回 100 页),典型见 Google、淘宝、京东的搜索结果页。
  • 详情,展示一个数据的尽可能多的内容。

如下是一个 Spring 框架,用于返回首页的示例:

@RequestMapping(value="/")
public ModelAndView homePage(){
   return new ModelAndView("/WEB-INF/jsp/index.jsp");
}

对于某个详情页面来说,它可能是这样的:

@RequestMapping(value="/detail/{detailId}")
public ModelAndView detail(HttpServletRequest request, ModelMap model){
   ....
   return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail);
}

那么,在微服务的情况下,它则会变成这样子:

@RequestMapping("/name")
public String name(){
    String name = restTemplate.getForObject("http://account/name", String.class);
    return Name" + name;
}

而后端在这个过程中,多了一个服务发现的服务,来管理不同微服务的关系。

前端:组件调用 -> 应用调用

在形式上来说,单体前端框架的路由和单体后端应用,并没有太大的区别:依据不同的路由,来返回不同页面的模板。

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
  { path: 'detail/:id', component: DetailComponent },
];

而当我们将之微服务化后,则可能变成应用 A 的路由:

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
];

外加之应用 B 的路由:

const appRoutes: Routes = [
  { path: 'detail/:id', component: DetailComponent },
];

而问题的关键就在于:怎么将路由分发到这些不同的应用中去。与此同时,还要负责管理不同的前端应用。

路由分发式微前端

路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。

就当前而言,通过路由分发式的微前端架构应该是采用最多、最易采用的 “微前端” 方案。但是这种方式看上去更像是多个前端应用的聚合,即我们只是将这些不同的前端应用拼凑到一起,使他们看起来像是一个完整的整体。但是它们并不是,每次用户从 A 应用到 B 应用的时候,往往需要刷新一下页面。

在几年前的一个项目里,我们当时正在进行遗留系统重写。我们制定了一个迁移计划:

  1. 首先,使用静态网站生成动态生成首页
  2. 其次,使用 React 计划栈重构详情页
  3. 最后,替换搜索结果页

整个系统并不是一次性迁移过去,而是一步步往下进行。因此在完成不同的步骤时,我们就需要上线这个功能,于是就需要使用 Nginx 来进行路由分发。

如下是一个基于路由分发的 Nginx 配置示例:

http {
  server {
    listen       80;
    server_name  www.phodal.com;
    location /api/ {
      proxy_pass http://http://172.31.25.15:8000/api;
    }
    location /web/admin {
      proxy_pass http://172.31.25.29/web/admin;
    }
    location /web/notifications {
      proxy_pass http://172.31.25.27/web/notifications;
    }
    location / {
      proxy_pass /;
    }
  }
}

在这个示例里,不同的页面的请求被分发到不同的服务器上。

随后,我们在别的项目上也使用了类似的方式,其主要原因是:跨团队的协作。当团队达到一定规模的时候,我们不得不面对这个问题。除此,还有 Angluar 跳崖式升级的问题。于是,在这种情况下,用户前台使用 Angular 重写,后台继续使用 Angular.js 等保持再有的技术栈。在不同的场景下,都有一些相似的技术决策。

因此在这种情况下,它适用于以下场景:

  • 不同技术栈之间差异比较大,难以兼容、迁移、改造
  • 项目不想花费大量的时间在这个系统的改造上
  • 现有的系统在未来将会被取代
  • 系统功能已经很完善,基本不会有新需求

而在满足上面场景的情况下,如果为了更好的用户体验,还可以采用 iframe 的方式来解决。

使用 iFrame 创建容器

iFrame 作为一个非常古老的,人人都觉得普通的技术,却一直很管用。

HTML 内联框架元素<iframe> 表示嵌套的正在浏览的上下文,能有效地将另一个 HTML 页面嵌入到当前页面中。

iframe 可以创建一个全新的独立的宿主环境,这意味着我们的前端应用之间可以相互独立运行。采用 iframe 有几个重要的前提:

  • 网站不需要 SEO 支持
  • 拥有相应的应用管理机制

如果我们做的是一个应用平台,会在我们的系统中集成第三方系统,或者多个不同部门团队下的系统,显然这是一个不错的方案。一些典型的场景,如传统的 Desktop 应用迁移到 Web 应用:

Angular Tabs 示例

如果这一类应用过于复杂,那么它必然是要进行微服务化的拆分。因此,在采用 iframe 的时候,我们需要做这么两件事:

  • 设计管理应用机制
  • 设计应用通讯机制

加载机制。在什么情况下,我们会去加载、卸载这些应用;在这个过程中,采用怎样的动画过渡,让用户看起来更加自然。

通讯机制。直接在每个应用中创建 postMessage 事件并监听,并不是一个友好的事情。其本身对于应用的侵入性太强,因此通过 iframeEl.contentWindow 去获取 iFrame 元素的 Window 对象是一个更简化的做法。随后,就需要定义一套通讯规范:事件名采用什么格式、什么时候开始监听事件等等。

有兴趣的读者,可以看看笔者之前写的微前端框架:Mooa

不管怎样,iframe 对于我们今年的 KPI 怕是带不来一丝的好处,那么我们就去造个轮子吧。

自制框架兼容应用

不论是基于 Web Components 的 Angular,或者是 VirtualDOM 的 React 等,现有的前端框架都离不开基本的 HTML 元素 DOM。

那么,我们只需要:

  1. 在页面合适的地方引入或者创建 DOM
  2. 用户操作时,加载对应的应用(触发应用的启动),并能卸载应用。

第一个问题,创建 DOM 是一个容易解决的问题。而第二个问题,则一点儿不容易,特别是移除 DOM 和相应应用的监听。当我们拥有一个不同的技术栈时,我们就需要有针对性设计出一套这样的逻辑。

尽管 Single-SPA 已经拥有了大部分框架(如 React、Angular、Vue 等框架)的启动和卸载处理,但是它仍然不是适合于生产用途。当我基于 Single-SPA 为 Angular 框架设计一个微前端架构的应用时,我最后选择重写一个自己的框架,即 Mooa

虽然,这种方式的上手难度相对比较高,但是后期订制及可维护性比较方便。在不考虑每次加载应用带来的用户体验问题,其唯一存在的风险可能是:第三方库不兼容

但是,不论怎样,与 iFrame 相比,其在技术上更具有可吹牛逼性,更有看点。同样的,与 iframe 类似,我们仍然面对着一系列的不大不小的问题:

  • 需要设计一套管理应用的机制。
  • 对于流量大的 toC 应用来说,会在首次加载的时候,会多出大量的请求

而我们即又要拆分应用,又想 blabla……,我们还能怎么做?

组合式集成:将应用微件化

组合式集成,即通过软件工程的方式在构建前、构建时、构建后等步骤中,对应用进行一步的拆分,并重新组合。

从这种定义上来看,它可能算不上并不是一种微前端——它可以满足了微前端的三个要素,即:独立运行独立开发独立部署。但是,配合上前端框架的组件 Lazyload 功能——即在需要的时候,才加载对应的业务组件或应用,它看上去就是一个微前端应用。

与此同时,由于所有的依赖、Pollyfill 已经尽可能地在首次加载了,CSS 样式也不需要重复加载。

常见的方式有:

  • 独立构建组件和应用,生成 chunk 文件,构建后再归类生成的 chunk 文件。(这种方式更类似于微服务,但是成本更高)
  • 开发时独立开发组件或应用,集成时合并组件和应用,最后生成单体的应用。
  • 在运行时,加载应用的 Runtime,随后加载对应的应用代码和模板。

应用间的关系如下图所示(其忽略图中的 “前端微服务化”):

组合式集成对比

这种方式看上去相当的理想,即能满足多个团队并行开发,又能构建出适合的交付物。

但是,首先它有一个严重的限制:必须使用同一个框架。对于多数团队来说,这并不是问题。采用微服务的团队里,也不会因为微服务这一个前端,来使用不同的语言和技术来开发。当然了,如果要使用别的框架,也不是问题,我们只需要结合上一步中的自制框架兼容应用就可以满足我们的需求。

其次,采用这种方式还有一个限制,那就是:规范!规范!规范!。在采用这种方案时,我们需要:

  • 统一依赖。统一这些依赖的版本,引入新的依赖时都需要一一加入。
  • 规范应用的组件及路由。避免不同的应用之间,因为这些组件名称发生冲突。
  • 构建复杂。在有些方案里,我们需要修改构建系统,有些方案里则需要复杂的架构脚本。
  • 共享通用代码。这显然是一个要经常面对的问题。
  • 制定代码规范。

因此,这种方式看起来更像是一个软件工程问题。

现在,我们已经有了四种方案,每个方案都有自己的利弊。显然,结合起来会是一种更理想的做法。

考虑到现有及常用的技术的局限性问题,让我们再次将目光放得长远一些。

纯 Web Components 技术构建

在学习 Web Components 开发微前端架构的过程中,我尝试去写了我自己的 Web Components 框架:oan。在添加了一些基本的 Web 前端框架的功能之后,我发现这项技术特别适合于作为微前端的基石

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 Web 应用中使用它们。

它主要由四项技术组件:

  • Custom elements,允许开发者创建自定义的元素,诸如 <today-news></today-news>。
  • Shadow DOM,即影子 DOM,通常是将 Shadow DOM 附加到主文档 DOM 中,并可以控制其关联的功能。而这个 Shadow DOM 则是不能直接用其它主文档 DOM 来控制的。
  • HTML templates,即 <template><slot> 元素,用于编写不在页面中显示的标记模板。
  • HTML Imports,用于引入自定义组件。

每个组件由 link 标签引入:

<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">

随后,在各自的 HTML 文件里,创建相应的组件元素,编写相应的组件逻辑。一个典型的 Web Components 应用架构如下图所示:

Web Components 架构

可以看到这边方式与我们上面使用 iframe 的方式很相似,组件拥有自己独立的 ScriptsStyles,以及对应的用于单独部署组件的域名。然而它并没有想象中的那么美好,要直接使用 Web Components 来构建前端应用的难度有:

  • 重写现有的前端应用。是的,现在我们需要完成使用 Web Components 来完成整个系统的功能。
  • 上下游生态系统不完善。缺乏相应的一些第三方控件支持,这也是为什么 jQuery 相当流行的原因。
  • 系统架构复杂。当应用被拆分为一个又一个的组件时,组件间的通讯就成了一个特别大的麻烦。

Web Components 中的 ShadowDOM 更像是新一代的前端 DOM 容器。而遗憾的是并不是所有的浏览器,都可以完全支持 Web Components。

结合 Web Components 构建

Web Components 离现在的我们太远,可是结合 Web Components 来构建前端应用,则更是一种面向未来演进的架构。或者说在未来的时候,我们可以开始采用这种方式来构建我们的应用。好在,已经有框架在打造这种可能性。

就当前而言,有两种方式可以结合 Web Components 来构建微前端应用:

  • 使用 Web Components 构建独立于框架的组件,随后在对应的框架中引入这些组件
  • 在 Web Components 中引入现有的框架,类似于 iframe 的形式

前者是一种组件式的方式,或者则像是在迁移未来的 “遗留系统” 到未来的架构上。

在 Web Components 中集成现有框架

现有的 Web 框架已经有一些可以支持 Web Components 的形式,诸如 Angular 支持的 createCustomElement,就可以实现一个 Web Components 形式的组件:

platformBrowser()
    .bootstrapModuleFactory(MyPopupModuleNgFactory)
        .then(({injector}) => {
            const MyPopupElement = createCustomElement(MyPopup, {injector});
            customElements.define(‘my-popup’, MyPopupElement);
});

在未来,将有更多的框架可以使用类似这样的形式,集成到 Web Components 应用中。

集成在现有框架中的 Web Components

另外一种方式,则是类似于 Stencil 的形式,将组件直接构建成 Web Components 形式的组件,随后在对应的诸如,如 React 或者 Angular 中直接引用。

如下是一个在 React 中引用 Stencil 生成的 Web Components 的例子:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import 'test-components/testcomponents';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

在这种情况之下,我们就可以构建出独立于框架的组件。

同样的 Stencil 仍然也只是支持最近的一些浏览器,比如:Chrome、Safari、Firefox、Edge 和 IE11

复合型

复合型,对就是上面的几个类别中,随便挑几种组合到一起。

我就不废话了~~。

结论

那么,我们应该用哪种微前端方案呢?答案见下一篇《微前端快速选型指南》

相关资料:

查看原文

赞 151 收藏 187 评论 5

phodal 发布了文章 · 2018-06-12

为什么微前端开始在流行:后端解耦,前端聚合

采用新技术,更多不是因为先进,而是因为它能解决痛点。

过去,我一直有一个疑惑,人们是否真的需要微服务,是否真的需要微前端。毕竟,没有银弹。当人们考虑是否采用一种新的架构,除了考虑它带来好处之外,仍然也考量着存在的大量的风险和技术挑战。

前端遗留系统迁移

自微前端框架 Mooa 及对应的《微前端的那些事儿》发布的两个多月以来,我陆陆续续地接收到一些微前端架构的一些咨询。过程中,我发现了一件很有趣的事:解决遗留系统,才是人们采用微前端方案最重要的原因

这些咨询里,开发人员所遇到的情况,与我之前遇到的情形并相似,我的场景是:设计一个新的前端架构。他们开始考虑前端微服务化,是因为遗留系统的存在。

过去那些使用 Backbone.js、Angular.js、Vue.js 1 等等框架所编写的单页面应用,已经在线上稳定地运行着,也没有新的功能。对于这样的应用来说,我们也没有理由浪费时间和精力重写旧的应用。这里的那些使用旧的、不再使用的技术栈编写的应用,可以称为遗留系统。而,这些应用又需要结合到新应用中使用。我遇到的较多的情况是:旧的应用使用的是 Angular.js 编写,而新的应用开始采用 Angular 2+。这对于业务稳定的团队来说,是极为常见的技术栈。

在即不重写原有系统的基础之下,又可以抽出人力来开发新的业务。其不仅仅对于业务人员来说, 是一个相当吸引力的特性;对于技术人员来说,不重写旧的业务,同时还能做一些技术上的挑战,也是一件相当有挑战的事情。

后端解耦,前端聚合

而前端微服务的一个卖点也在这里,去兼容不同类型的前端框架。这让我又联想到微服务的好处,及许多项目落地微服务的原因:

在初期,后台微服务的一个很大的卖点在于,可以使用不同的技术栈来开发后台应用。但是,事实上,采用微服务架构的组织和机构,一般都是中大型规模的。相较于中小型,对于框架和语言的选型要求比较严格,如在内部限定了框架,限制了语言。因此,在充分使用不同的技术栈来发挥微服务的优势这一点上,几乎是很少出现的。在这些大型组织机构里,采用微服务的原因主要还是在于,使用微服务架构来解耦服务间依赖

而在前端微服务化上,则是恰恰与之相反的,人们更想要的结果是聚合,尤其是那些 To B(to Bussiness)的应用。

在这两三年里,移动应用出现了一种趋势,用户不想装那么多应用了。而往往一家大的商业公司,会提供一系列的应用。这些应用也从某种程度上,反应了这家公司的组织架构。然而,在用户的眼里他们就是一家公司,他们就只应该有一个产品。相似的,这种趋势也在桌面 Web 出现。聚合成为了一个技术趋势,体现在前端的聚合就是微服务化架构。

兼容遗留系统

那么,在这个时候,我们就需要使用新的技术、新的架构,来容纳、兼容这些旧的应用。而前端微服务化,正好是契合人们想要的这个卖点呗了。

你说呢?

查看原文

赞 8 收藏 7 评论 0

phodal 分享了头条 · 2018-05-07

某天,我在微信群里发了一段代码,突然就有了一个想法——我应该做一个这样的小程序:它可以很方便的在微信群里分享代码。

赞 2 收藏 0 评论 1

phodal 发布了文章 · 2018-03-19

如何解构单体前端应用——微前端应用的微服务式拆分

刷新页面?路由拆分?No,动态加载组件。

本文分为以下四部分:

  • 前端微服务化思想介绍
  • 微前端的设计理念
  • 实战微前端架构设计
  • 基于 Mooa 进行前端微服务化

前端微服化

对于前端微服化来说,有这么一些方案:

  • Web Component 显然可以一个很优秀的基础架构。然而,我们并不可能去大量地复写已有的应用。
  • iFrame。你是说真的吗?
  • 另外一个微前端框架 Single-SPA,显然是一个更好的方式。然而,它并非 Production Ready。
  • 通过路由来切分应用,而这个跳转会影响用户体验。
  • 等等。

因此,当我们考虑前端微服务化的时候,我们希望:

  • 独立部署
  • 独立开发
  • 技术无关
  • 不影响用户体验

独立开发

在过去的几星期里,我花费了大量的时间在学习 Single-SPA 的代码。但是,我发现它在开发和部署上真的太麻烦了,完全达不到独立部署地标准。按 Single-SPA 的设计,我需要在入口文件中声名我的应用,然后才能去构建:

declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), pathPrefix('/inferno'));

同时,在我的应用里,我还需要去指定我的生命周期。这就意味着,当我开发了一个新的应用时,必须更新两份代码:主工程和应用。这时我们还极可能在同一个源码里工作。

当出现多个团队的时候,在同一份源码里工作,显然变得相当的不可靠——比如说,对方团队使用的是 Tab,而我们使用的是 2 个空格,隔壁的老王用的是 4 个空格。

独立部署

一个单体的前端应用最大的问题是,构建出来的 js、css 文件相当的巨大。而微前端则意味着,这个文件被独立地拆分成多个文件,它们便可以独立去部署应用。

我们真的需要技术无关吗?

等等,我们是否真的需要技术无关?如果我们不需要技术无关的话,微前端问题就很容易解决了。

事实上,对于大部分的公司和团队来说,技术无关只是一个无关痛痒的话术。当一家公司的几个创始人使用了 Java,那么极有可能在未来的选型上继续使用 Java。除非,一些额外的服务来使用 Python 来实现人工智能。因此,在大部分的情况下,仍然是技术栈唯一。

对于前端项目来说,更是如此:一个部门里基本上只会选用一个框架。

于是,我们选择了 Angular。

不影响用户体验

使用路由跳转来进行前端微服务化,是一种很简单、高效的切分方式。然而,路由跳转地过程中,会有一个白屏的过程。在这个过程中,跳转前的应用和将要跳转的应用,都失去了对页面的控制权。如果这个应用出了问题,那么用户就会一脸懵逼。

理想的情况下,它应该可以被控制。

微前端的设计理念

设计理念一:中心化路由

互联网本质是去中心化的吗?不,DNS 决定了它不是。TAB,决定了它不是。

微服务从本质上来说,它应该是去中心化的。但是,它又不能是完全的去中心化。对于一个微服务来说,它需要一个服务注册中心

服务提供方要注册通告服务地址,服务的调用方要能发现目标服务。

对于一个前端应用来说,这个东西就是路由。

Menu

从页面上来说,只有我们在网页上添加一个菜单链接,用户才能知道某个页面是可以使用的。

404

而从代码上来说,那就是我们需要有一个地方来管理我们的应用:**发现存在哪些应用,哪个应用使用哪个路由。

管理好我们的路由,实际上就是管理好我们的应用

设计理念二:标识化应用

在设计一个微前端框架的时候,为每个项目取一个名字的问题纠结了我很久——怎么去规范化这个东西。直到,我再一次想到了康威定律:

系统设计(产品结构等同组织形式,每个设计系统的组织,其产生的设计等同于组织之间的沟通结构。

康威定律

换句人话说,就是同一个组织下,不可能有两个项目的名称是一样的。

所以,这个问题很简单就解决了。

设计理念三:生命周期

Single-SPA 设计了一个基本的生命周期(虽然它没有统一管理),它包含了五种状态:

  • load,决定加载哪个应用,并绑定生命周期
  • bootstrap,获取静态资源
  • mount,安装应用,如创建 DOM 节点
  • unload,删除应用的生命周期
  • unmount,卸载应用,如删除 DOM 节点

于是,我在设计上基本上沿用了这个生命周期。显然,诸如 load 之类对于我的设计是多余的。

设计理念四:独立部署与配置自动化

从某种意义上来说,整个每系统是围绕着应用配置进行的。如果应用的配置能自动化,那么整个系统就自动化。

当我们只开发一个新的组件,那么我们只需要更新我们的组件,并更新配置即可。而这个配置本身也应该是能自动生成的。

实战微前端架构设计

基于以上的前提,系统的工作流程如下所示:

系统工作流

整体的工程流程如下所示:

  1. 主工程在运行的时候,会去服务器获取最新的应用配置。
  2. 主工程在获取到配置后,将一一创建应用,并为应用绑定生命周期。
  3. 当主工程监测到路由变化的时候,将寻找是否有对应的路由匹配到应用。
  4. 当匹配对对应应用时,则加载相应的应用。

故而,其对应的架构如下图所示:

Architecture

独立部署与配置自动化

我们做的部署策略如下:我们的应用使用的配置文件叫 apps.json,由主工程去获取这个配置。每次部署的时候,我们只需要将 apps.json 指向最新的配置文件即可。配置的文件类如下所示:

  1. 96a7907e5488b6bb.json
  2. 6ff3bfaaa2cd39ea.json
  3. dcd074685c97ab9b.json

一个应用的配置如下所示:

{
  "name": "help",
  "selector": "help-root",
  "baseScriptUrl": "/assets/help",
  "styles": [
    "styles.bundle.css"
  ],
  "prefix": "help",
  "scripts": [
    "inline.bundle.js",
    "polyfills.bundle.js",
    "main.bundle.js"
  ]
}

这里的 selector 对应于应用所需要的 DOM 节点,prefix 则是用于 URL 路由上。这些都是自动从 index.html 文件和 package.json 中获取生成的。

应用间路由——事件

由于现在的应用变成了两部分:主工程和应用部分。就会出现一个问题:只有一个工程能捕获路由变化。当由主工程去改变应用的二级路由时,就无法有效地传达到子应用。在这时,只能通过事件的方式去通知子应用,子应用也需要监测是否是当前应用的路由。

if (event.detail.app.name === appName) {
  let urlPrefix = 'app'
  if (urlPrefix) {
    urlPrefix = `/${window.mooa.option.urlPrefix}/`
  }
  router.navigate([event.detail.url.replace(urlPrefix + appName, '')])
}

相似的,当我们需要从应用 A 跳转到应用 B 时,我们也需要这样的一个机制:

window.addEventListener('mooa.routing.navigate', function(event: CustomEvent) {
  const opts = event.detail
  if (opts) {
    navigateAppByName(opts)
  }
})

剩下的诸如 Loading 动画也是类似的。

使用 Mooa 进行

So,我们就有了前端微服务框架 Mooa。它基于 single-spa && single-spa-angular-cli,并且符合以上的设计思想。

GayHub 地址:https://github.com/phodal/mooa

对于主工程而言,只需要以下的几行代码就可以完成上面的功能:

http.get<any[]>('/assets/apps.json')
  .subscribe(data => {
    data.map((config) => {
      that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
    });
    this.mooa.start();
  });

this.router.events.subscribe((event: any) => {
  if (event instanceof NavigationEnd) {
    that.mooa.reRouter(event);
  }
});

并添加一个对应的子应用路由:

{
  path: 'app/:appName/:route',
  component: HomeComponent
}

则如上所述的四个步骤。

对于子工程而言,则只需要一个对应的 Hook 操作:

mooaPlatform.mount('help').then((opts) => {
  platformBrowserDynamic().bootstrapModule(AppModule).then((module) => {
    opts['attachUnmount'](module);
  });
});

并设置好对应的 base_href:

providers: [
  {provide: APP_BASE_HREF, useValue: mooaPlatform.appBase()},
]

嗯,就是这么简单。DEMO 视频如下:

Demo 地址见:http://mooa.phodal.com/

GitHub 示例:https://github.com/phodal/mooa

查看原文

赞 18 收藏 27 评论 3