Fork me on GitHub
秋染蒹葭

用状态机做一个云构建平台

最近在升级团队云构建的工具,由于构建任务的状态跳转很复杂,早期的IF-ELSE写法已经无法满足任务管理的扩展了,比如我想加一个代码规则的校验,那我就需要加上新的条件分支,各种IF-ELSE散落到各种异步逻辑中,这就给后续产品逻辑调整继续增加负担,因此决定用状态机来描述构建任务的流转,让逻辑变得可预测。

本文的重点是介绍用状态机描述一个云构建任务的流转,具体的构建方式可以根据自己团队的实际情况来设计

闲言少叙,开始

云构建是什么

顾名思义,就是在服务端进行代码的build。体现在前端就是通过在云端提供的node环境上,运行打包工具,将源代码构建成浏览器可执行的代码,并将打包物返回给调用方的方式
目前很多在用的travis、gitlab-ci都是一种云构建的工具,通过简单的配置的就可以进行自动化的构建,比如博主在构建github page时,在github上用到的travis的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ʹ������
language: node_js
node_js: "10"
branches:
only:
- master
cache:
apt: true
yarn: true
directories:
- node_modules
before_install:
- git config --global user.name "xxx"
- git config --global user.email "xxx@xxx.com"
- curl -o- -L https://yarnpkg.com/install.sh | bash
- export PATH=$HOME/.yarn/bin:$PATH
- npm install -g hexo-cli
install:
- yarn
script:
- hexo clean
- hexo generate
after_success:
- cd ./public
- git init
- git add --all .
- git commit -m "Travis CI Auto Builder"
- git push --quiet --force https://$REPO_TOKEN@github.com/zhyjor/zhyjor.github.io
master

gitlab ci上配置也差不多,这样云端的构建基本就完成了;自己的项目中完全可以在success后,将构建物推到cdn上,然后通过cms工具发布一下html即可,这样一个简单的云构建流程就起来了

为什么需要云构建呢

博主之前遇到多次由于打包环境不一致引发的线上问题,引起环境不一致的可能是node版本不一致引发的问题,也可能是构建时代码未进行pull造成功能缺失等等,总结下有一下几点

  • 提供纯净的无干扰的环境,避免本地环境不同(node、os等),引起构建物的差异
  • 提供可溯源的管控平台,可对项目发布进行管理
  • 避免代码未提交引起线上分支功能缺失问题
  • 记录构建日志、构建时长、分析依赖,未项目优化提供数据支持

状态机是什么

前端场景下说到状态机,就不可避免的谈到状态管理,使用vue全局状态管理一般是vuex,react一般是Redux。UI=Fx(Data),使用了这些框架后,前端写的大部分业务逻辑都是在管理状态,然后框架帮我吗映射为UI

随着业务的逐渐膨胀,业务逻辑层(数据层)逐渐变成了一团逐渐失控的代码,状态,由于本文重点不是讨论状态管理框架的,这里贴一张克军大佬的ppt,很明确的表现了复杂逻辑下,状态失控的问题

复杂的业务逻辑带来的是太多的逻辑分支,各种IF-ELSE分散在不同的组件内,造成代码难阅读,难扩展

使用状态的好处

有限状态机,是表示多个有限状态以及这些状态间的转移、动作的数学模型,通过构建这个数学模型,将业务逻辑描述出来;通俗来说,状态机的描述文件就是一个剧本,通过这个剧本,逻辑就变得可预测了

XState

我们选择了XState作为云构建的状态机框架,状态机的库也挺多的,XState、JavaScript-State-Machine、Robot等,XState的优势是文档更好,同时具有可视化工具,使用VSCode的扩展可以很方便的进行状态机设计

状态机设计

状态机设计这里社区没什么最优实践,也没什么明确的方法论,只能结合自己的业务逻辑给出一个适合自己的方案

场景分析

一个云构建任务分为如下的几个过程,其中check阶段作为并行任务实现

由上图可得出状态机的简单描述文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { createMachine, interpret, assign } from 'xstate';
function startAsyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new Date().getTime());
}, 1000);
})
}
const buildMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QCMCuBLANhAdOimYAxAAoBKAoiQIKUD6FAcgGoDaADALqKgAOA9rHQAXdPwB2PEAA9EARgDMcnO1WqATAE45ADl3qdAdgA0IAJ6J1AFnU5DmvQFYdVw1Z3tNhnQF8fptCxcCH4AYwBrMAAnIhDxMDxxADd+SJxYYQBDKOEAYQks9Hiojm4kEAEhUQkpWQQrR00cBU12ADY5dS7FDp1TCwR1BQUcLzbHKwUrTq05Nr8AjGwcEIjoomio-iicXkxM4QAzbYBbdKyc-PFC4tKpSpExSXK6hqaW9pn1Ht1++SnRpogZoFIZQZour5-CBAstQpgJMQ4gkiik0lARLkEfE7uUHtVnqA6jpQYDvo42oZHHNXJo-oNHIYcFY2u45A57FMjAsYUtcLCILFEYk0QksrBwgAhPm4viCR41F7yORUnA6AzsHTOdiOdgqkzmSyM5msvQckHuQw8gW7VDITDoWAACyF8RFqQSvDtDudABV+LkIOJZRV5QTavJqUzXOMddYdBCFG16epjSy2eauVboTbDpksKgosRKL6yABNEP4p4RhAUxzNNwOdkJ9RtbT09WA4FtsZtdUKPzQ8T8CBwKQ2-CEe5h6tK+rqFMAzSmuaswxDBMDnN8lZhSJRadVWdEyzLnAGVqNORyaYKPqGwbs89N9dtJQKdTsUHWnfwxGHhVCRkRBKSsNVWjaL9WSsGxpkXEZl3cdpE3UNx2GzRYghwAUAPDOc5D1WxX0cD820MewrHg0ZTSmL9nEaVCfywr17UdJ1cOPYCEEUCYGysTRHG+aYezpB8hgQmirDorUIQw3ksNgVBQlCOB4DxGdFRPbj1yaIx+ME-jVAhFM01NLRtG8Dp1CY5Y8wLIsOM0rjQPA9ooJg2C5HpORBOZHR1RcRQtAmOYbIgRygLqABaZMHxilQ1EUTxIR1KxBx8IA */
createMachine({
context: {
id: 42,
repo_url: undefined,
recordTime: {
docker: { start: undefined, end: undefined },
clone: { start: undefined, end: undefined },
install: { start: undefined, end: undefined },
build: { start: undefined, end: undefined },
},
imageUrl: "node:16",
},
id: "build",
initial: "idle",
states: {
idle: {
on: {
PREPARE_ENV: {
target: "docker",
},
},
},
docker: {
invoke: {
id: "startContainer",
src: (context, event) => {
// 更新一下一下时间
const dockerTimeStart = new Date().getTime();
const dockerTime = context.recordTime.docker;
dockerTime.start = dockerTimeStart;
context.recordTime.docker = dockerTime;
assign({ recordTime: { ...context.recordTime } });
// image start
return startAsyncTask(context.imageUrl);
},
onDone: [
{
target: "clone",
actions: assign({
recordTime: (context, event) => {
const dockerTime = context.recordTime.docker;
dockerTime.end = event.data;
context.recordTime.docker = dockerTime;
return context.recordTime;
}
}),
},
],
onError: [
{
target: "failure",
actions: assign({ error: (context, event) => event.data }),
},
],
},
},
clone: {
invoke: {
src: (context, event) => {
return startAsyncTask(context.repo_url);
},
id: "gitClone",
onDone: [
{
target: "build",
},
],
},
},
build: {
invoke: {
src: (context, event) => {
return startAsyncTask();
},
id: "taskBuild",
onDone: [
{
target: "publish",
},
],
},
},
publish: {
invoke: {
src: (context, event) => {
return startAsyncTask();
},
id: "publishToCdn",
onDone: [
{
target: "success",
},
],
},
},
success: {
type: "final",
},
failure: {
type: "final",
on: {
RETRY: {
target: "idle",
},
},
},
},
});
const buildService = interpret(buildMachine).onTransition((state) => {
console.log(state.value);
if (state.value === 'clone') {
console.log(state.context.recordTime);
}
});
buildService.start();
buildService.send('PREPARE_ENV');
// 输出
idle
docker
clone
{
docker: { start: 1668248311128, end: 1668248312130 },
clone: { start: undefined, end: undefined },
install: { start: undefined, end: undefined },
build: { start: undefined, end: undefined }
}
build
publish
success

总结

上述用状态机模拟了一个简单的构建过程,其实还有很多值得深究的地方,比如check阶段使用并行状态来进行多种检查等等,另外构建平台也有更多提高稳定性的逻辑,gitlab-ci构建方式也有docker in docker、bash等不同的方式,有兴趣的话可以后续再更新几篇关于云构建的文章

最后的最后,欢迎关注个人的github博客

参考资料

本文标题:用状态机做一个云构建平台

文章作者:zhyjor

发布时间:2022年11月12日 - 10:11

最后更新:2023年10月11日 - 02:10

原始链接:https://zhyjor.github.io/2022/11/12/用状态机做一个云构建平台/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

🐶 您的支持将鼓励我继续创作 🐶

热评文章