点亮 ⭐️ Star · 照亮开源之路
https://github.com/apache/dolphinscheduler

动态任务组件是 Apache DolphinScheduler 以动态表单渲染(它包含表单结构的渲染,表单的配置项等)的方式实现工作流 Dag节点配置渲染。区别于现有任务组件的实现方式,动态任务组件的实现在于通过接口获取任务的分类、任务节点以及每个任务对应的表单结构。
本文将介绍 Apache DolphinScheduler 初版动态任务组件设计的实现方案,希望帮助大家更高效地理解和使用海豚调度。
01
初衷
减少前端部分的代码,缩小打包后的体积 尽可能在新增、编辑和删除一个任务组件时无需重新打包构建前端代码 便于后端开发者快速且无障碍的对任务组件进行开发(无需关注前端代码逻辑)
02
设计方案
增加系统设置页面(该页面目前包含接口默认请求的超时时间配置和日志接口轮询的时间间隔配置),在页面中增加实验性功能配置,其中包含动态任务组件的开关选项(当开关打开时,会在工作流定义页面中出现一个创建工作流(动态)的黄色按钮,点击后跳转新的画布页面); 

画布的小地图功能; 
左侧的任务组件列表由任务组件分类和查询当前分类下的任务组件两个接口的数据进行前端页面的渲染(任务组件的名称、默认图标和鼠标悬浮的图标都以静态资源文件形式存放在后端资源目录中); 当拖拽 Shell
任务到画布的过程中调用一个接口,该接口会返回此任务的表单结构数据(表单结构数据是以JSON
文件的形式存放在后端资源目录中)。
该方案的实现是以兼容模式进行处理,既要保证现有的逻辑能够平稳运行,又要保证新功能的不断迭代; 初版不支持跑通整个工作流的业务; 初版不支持画布中节点与节点之间的连接线功能; 初版只支持 Shell
组件中的四个必填字段;初版的表单项只支持 studio
、input
和select
,其中select
支持动态options
和非动态options
。
03
表单结构化数据剖析
基础结构
name:主要用于表示任务组件的名称。当然,它也有一些其它的用途,例如我们采用了动态任务组件的方案,网页的文档结构是根据数据进行渲染的,所以为了方便 E2E
测试,我们会用此字段和forms
中的field
字段进行拼接作为标签的class
属性的值;locales:目前 DolphinScheduler
的项目只支持中文和英语两种语言,所以locales
中只存在这两个对象结构;apis:用于存放需要调用的接口; forms: 用于存放表单项的数据。
{
"name": "",
"locales": {
"zh_CN": {},
"en_US": {}
},
"apis": {},
"forms": []
}
locals
在需要 使用 多语言的地方,需要增加 task_components
前缀,如果不增加前缀则不会进行多语言的处理;多语言的内容需要根据表单项进行调整。
{
"zh_CN": {
"node_name": "节点名称",
"script": "脚本"
},
"en_US": {
"node_name": "Node Name",
"script": "Script"
}
}
apis
初版动态任务组件的设计方案中针对动态的接口请求只支持 select
组件的动态options
;初版的接口请求方式只支持 get
;初版的接口不支持传参,且返回类型为 ["aaa", "bbb", "ccc"]
形式;对于 url
字段,开发者无需增加dolphinscheduler
前缀,因为它已经内置在请求拦截器中了。
{
"apiName": {
"url": "",
"method": "get"
}
}
forms
基础节点配置
input或
select节点的对象要合并在一起。
label(支持多语言):表单项的名称(如果不增加 task_components
前缀,则中英文下都会显示为node_name
)type:表单项的类型,初版只支持 studio
、input
和selectfield:提交表单时接口需要绑定的字段(如果存在接口入参格式是嵌套的情况 {a: {b: 1}}
则field
的值可写为"a.b"
, 对应的结构解析器、校验解析器和值解析器会同步处理该情况。但是初版只支持两层的对象嵌套,且初版有嵌套对象的表单项为脚本项)defaultValue:默认值, input
组件的默认值为""
,select
组件的默认值为nullvalidate:表单项的校验规则 validate.required:是否必填 validate.trigger:校验触发的方式, input
为值发生改变时触发,blur
为失焦时触发validate.type:校验类型(初版只支持非空校验) validate.message(支持多语言):校验不通过时页面展示的提示信息
{
"label": "task_components.node_name",
"type": "input",
"field": "",
"defaultValue": "",
"validate": {
"required": true,
"trigger": [
"input",
"blur"
],
"type": "non-empty",
"message": ""
}
}
Studio
studio类型没有任何特殊配置,使用基础节点配置已能满足需求,所以不做过多介绍。
Input
clearable:是否在有内容时显示清空按钮 placeholder(支持多语言): input
框内的提示信息textarea:是否显示为文本域
{
"clearable": true,
"placeholder": "",
"textarea": false
}
Select
静态
clearable:是否在有内容时显示清空按钮 placeholder(支持多语言): select
框内的提示信息options: select
中的选项数据,为空数组时则没有下拉数据options.label(支持多语言):页面中展现的下拉数据 options.value:该条下拉数据对应的唯一值
{
"clearable": true,
"placeholder": "",
"options": [
{
"label": "",
"value": 1
},
{
"label": "",
"value": 2
}
]
}
动态
options需要调用的接口名称(需要在基础结构
apis字段中做对应),并将请求后的数据覆盖到
select的
options字段中
{
"api": "apiName"
}
04
解析器
HTML、
CSS和
JavaScript进行渲染,为此,我们设计了一些解析器(由于是初版的原因,我们大体上对解析器做了一些设计,使它更加灵活的同时也为了方便后续的拓展)
setup生命周期中进行注册和绑定,但是
vue3的生命周期钩子最早的是
onBeforeMount且该数据是异步接口获取的数据 。同时,解析器的代码重在展示逻辑,为此会使用
any类型。
多语言解析器
locales字段,它会与本地的
vue-i18n进行并集处理
useI18n().mergeLocaleMessage('zh_CN', { task_components: locales.zh_CN })
useI18n().mergeLocaleMessage('en_US', { task_components: locales.en_US })
值解析器
forms字段中的
field字段解析成要提交的表单格式
解析
field字段(需要对嵌套对象结构进行处理)。
const model: any = {}
forms.forEach((f: any) => {
if (f.field.indexOf('.') >= 0) {
const hierarchy = f.field.split('.')
model[hierarchy[0]] = {
[hierarchy[1]]: setField(f.defaultValue, f.type)
}
} else {
model[f.field] = setField(f.defaultValue, f.type)
}
})
赋值
model进行赋值操作(如果
defaultValue存在则赋值为
defaultValue的值,否则会根据
input或
select进行默认值)。
import { ref } from 'vue'
import type { Ref } from 'vue'
const setField = (value: string, type: string): Ref<null | string> => {
return ref(value ?
value :
type === 'select' ?
null :
''
)
}
校验解析器
forms字段中的
validate字段。
解析
validate字段,在
validate不存在或
validate是空对象或
required为
false或
type不存在时,将不会处理当前表单项的校验逻辑。
field的逻辑时与值解析器不同。
const validate: any = {}
forms.forEach((f: any) => {
if (
!f.validate &&
Object.keys(f.validate).length <= 0 &&
!f.validate.required &&
!f.validate.type
) return
if (f.field.indexOf('.') >= 0) {
const hierarchy = f.field.split('.')
validate[hierarchy[1]] = setValidate(f.validate, f.field)
} else {
validate[f.field] = setValidate(f.validate)
}
})
包装
data,并将不会发生变化的
required和
trigger赋值进去。当该方法有
field参数传递进来时代表当前表单项的
type为
studio,对于这个类型并不是原生的表单项,所以需要通过直解析器处理后的值进行判断。
const setValidate = (validate: any, field?: any): object => {
const data: any = {
required: validate.required,
trigger: validate.trigger
}
if (validate.type === 'non-empty') {
data['validator'] = (rule: FormItemRule, value: string) => {
if (field) {
const hierarchy = field.split('.')
if (!model[hierarchy[0]][hierarchy[1]]) {
return Error(t(validate.message))
}
} else {
if (!value) {
return Error(t(validate.message))
}
}
}
}
return data
}
请求解析器
select的
options选项。
基础请求方法
axios。
const reqFunction = (url: string, method: 'get') => {
return axios({
url,
method
})
}
解析
api字段值是否在
apis中存在。
export function useFormRequest(apis: any, forms: Array<any>): Array<any> {
forms.map(f => {
if (f.api && apis[f.api]) {
reqFunction(apis[f.api].url, apis[f.api].method).then((res: any) => {
f.options = res.map((r: any) => {
return { label: r, value: r }
})
})
}
})
return forms
}
结构解析器
forms中的对于浏览器页面渲染和逻辑无关的参数(过滤掉这些无用的参数,能在浏览器渲染中节省一部分时间)。
export function useFormStructure(forms: Array<any>): Array<any> {
return forms.map((f: any) => {
delete f.validate
delete f.api
if (f.field.indexOf('.') >= 0) {
const hierarchy = f.field.split('.')
f.field = hierarchy[1]
}
return f
})
}
05
表单效果和配置数据
表单效果
表单的最终展现效果会根据配置数据进行渲染,修改表单信息只需要修改后端的 json 数据配置即可,而无需改动前端代码。

配置数据
json{task: 'shell',locales: {zh_CN: {node_name: '节点名称',node_name_tips: '请输入节点名称',node_name_validate_message: '节点名称不能为空',script_validate_message: '脚本内容不能为空',task_priority: '任务优先级',highest: '最高',high: '高',medium: '中',low: '低',lowest: '最低',worker_group: 'Worker 分组',script: '脚本'},en_US: {node_name: 'Node Name',node_name_tips: 'Please entry node name',node_name_validate_message: 'Node name cannot be empty',script_validate_message: 'Script content cannot be empty',task_priority: 'Task Priority',highest: 'Highest',high: 'High',medium: 'Medium',low: 'Low',lowest: 'Lowest',worker_group: 'Worker Group',script: 'Script'}},apis: {getWorkerGroupList: {url: '/worker-groups/all',method: 'get'}},forms: [{label: 'task_components.node_name',type: 'input',field: 'name',defaultValue: '',clearable: true,placeholder: 'task_components.node_name_tips',validate: {required: true,trigger: ['input', 'blur'],type: 'non-empty',message: 'task_components.node_name_validate_message'}},{label: 'task_components.task_priority',type: 'select',field: 'taskPriority',options: [{ label: 'task_components.highest', value: 'HIGHEST' },{ label: 'task_components.high', value: 'HIGH' },{ label: 'task_components.medium', value: 'MEDIUM' },{ label: 'task_components.low', value: 'LOW' },{ label: 'task_components.lowest', value: 'LOWEST' }],optionsLocale: true,defaultValue: 'MEDIUM',validate: {required: true,trigger: ['input', 'blur']}},{label: 'task_components.worker_group',type: 'select',field: 'workerGroup',options: [],optionsLocale: false,defaultValue: 'default',api: 'getWorkerGroupList',validate: {required: true,trigger: ['input', 'blur']}},{label: 'task_components.script',type: 'studio',field: 'taskParams.rawScript',defaultValue: '',validate: {required: true,trigger: ['input', 'blur'],type: 'non-empty',message: 'task_components.script_validate_message'}}]}
06
结语
lodash或
AST进行解析),同样地,我们的解析器也会面临着诸多改造以支持更广泛的任务组件的需求。所以,如果读到这篇文章的你有
DolphinScheduler 动态任务组件实现的更好的思路,也非常欢迎加入我们的贡献者种子群(联系人: easyworkflow)与我们交流, 我们相信贡献者的力量!
参与贡献
随着国内开源的迅猛崛起,Apache DolphinScheduler 社区迎来蓬勃发展,为了做更好用、易用的调度,真诚欢迎热爱开源的伙伴加入到开源社区中来,为中国开源崛起献上一份自己的力量,让本土开源走向全球。

参与 DolphinScheduler 社区有非常多的参与贡献的方式,包括:

贡献第一个PR(文档、代码) 我们也希望是简单的,第一个PR用于熟悉提交的流程和社区协作以及感受社区的友好度。
社区汇总了以下适合新手的问题列表:https://github.com/apache/dolphinscheduler/issues/5689
非新手问题列表:https://github.com/apache/dolphinscheduler/issues?q=is%3Aopen+is%3Aissue+label%3A%22volunteer+wanted%22
如何参与贡献链接:https://dolphinscheduler.apache.org/zh-cn/community/development/contribute.html
来吧,DolphinScheduler开源社区需要您的参与,为中国开源崛起添砖加瓦吧,哪怕只是小小的一块瓦,汇聚起来的力量也是巨大的。
参与开源可以近距离与各路高手切磋,迅速提升自己的技能,如果您想参与贡献,我们有个贡献者种子孵化群,可以添加社区小助手微信(Leonard-ds) ,手把手教会您( 贡献者不分水平高低,有问必答,关键是有一颗愿意贡献的心 )。

添加社区小助手微信(Leonard-ds)
添加小助手微信时请说明想参与贡献。
来吧,开源社区非常期待您的参与。
☞Apache DolphinScheduler 社区年度总结
☞Apache Dolphinscheduler 任务插件版图再添 Linkis,大幅提高计算治理能力
☞DolphinScheduler 快速构建 Hugging Face 文本分类工作流,基于工作流的机器学习训练部署太强了!





