Apache DolphinScheduler 动态任务组件设计实现方案,减少前端代码

点亮 ⭐️ Star · 照亮开源之路

https://github.com/apache/dolphinscheduler

c7259aa87506129d52a6da3c4793524f.jpeg

动态任务组件是 Apache DolphinScheduler 以动态表单渲染(它包含表单结构的渲染,表单的配置项等)的方式实现工作流 Dag节点配置渲染。区别于现有任务组件的实现方式,动态任务组件的实现在于通过接口获取任务的分类、任务节点以及每个任务对应的表单结构。

本文将介绍 Apache DolphinScheduler 初版动态任务组件设计的实现方案,希望帮助大家更高效地理解和使用海豚调度。

01

初衷

Apache DolphinScheduler 之所以通过动态任务组件的方式实现工作流 Dag节点配置渲染,是基于以下三点考虑:

  • 减少前端部分的代码,缩小打包后的体积

  • 尽可能在新增、编辑和删除一个任务组件时无需重新打包构建前端代码

  • 便于后端开发者快速且无障碍的对任务组件进行开发(无需关注前端代码逻辑)

02

设计方案

由于该方案是一个长期计划,所以我们目前只实现了初版的动态任务组件的功能,它包含以下几点功能和变化:

  • 增加系统设置页面(该页面目前包含接口默认请求的超时时间配置和日志接口轮询的时间间隔配置),在页面中增加实验性功能配置,其中包含动态任务组件的开关选项(当开关打开时,会在工作流定义页面中出现一个创建工作流(动态)的黄色按钮,点击后跳转新的画布页面);

    50af7dd49dd8382547c9ec321fee10b2.png

    b42abd3baed5b4bea0b1db36aeaf03c3.png

  • 画布的小地图功能;

    6d1e67d1d6134f9cd7240fdba1895365.png

  • 左侧的任务组件列表由任务组件分类和查询当前分类下的任务组件两个接口的数据进行前端页面的渲染(任务组件的名称、默认图标和鼠标悬浮的图标都以静态资源文件形式存放在后端资源目录中);

  • 当拖拽 Shell 任务到画布的过程中调用一个接口,该接口会返回此任务的表单结构数据(表单结构数据是以 JSON 文件的形式存放在后端资源目录中)。

目前这个方案依旧在初版阶段,所以需要注意以下几点:

  • 该方案的实现是以兼容模式进行处理,既要保证现有的逻辑能够平稳运行,又要保证新功能的不断迭代;

  • 初版不支持跑通整个工作流的业务;

  • 初版不支持画布中节点与节点之间的连接线功能;

  • 初版只支持 Shell 组件中的四个必填字段;

  • 初版的表单项只支持 studioinputselect ,其中 select 支持动态 options 和非动态 options

03

表单结构化数据剖析


为了最小化的验证该方案的可行性并考虑到兼容性的问题,我们并没有对后端做较大的改动。为此,我们在表单结构化的数据形式做了一些妥协。

01

基础结构

  • name:主要用于表示任务组件的名称。当然,它也有一些其它的用途,例如我们采用了动态任务组件的方案,网页的文档结构是根据数据进行渲染的,所以为了方便 E2E 测试,我们会用此字段和 forms 中的 field 字段进行拼接作为标签的 class 属性的值;

  • locales:目前 DolphinScheduler 的项目只支持中文和英语两种语言,所以 locales 中只存在这两个对象结构;

  • apis:用于存放需要调用的接口;

  • forms: 用于存放表单项的数据。

{
  "name": "",
  "locales": {
    "zh_CN": {},
    "en_US": {}
  },
  "apis": {},
  "forms": []
}

02

locals

  • 在需要 使用 多语言的地方,需要增加 task_components 前缀,如果不增加前缀则不会进行多语言的处理;

  • 多语言的内容需要根据表单项进行调整。

{
  "zh_CN": {
    "node_name": "节点名称",
    "script": "脚本"
  },
  "en_US": {
    "node_name": "Node Name",
    "script": "Script"
  }
}

03

apis

  • 初版动态任务组件的设计方案中针对动态的接口请求只支持 select 组件的动态 options

  • 初版的接口请求方式只支持 get

  • 初版的接口不支持传参,且返回类型为 ["aaa", "bbb", "ccc"] 形式;

  • 对于 url 字段,开发者无需增加 dolphinscheduler 前缀,因为它已经内置在请求拦截器中了。

{
  "apiName": {
    "url": "",
    "method": "get"
  }
}

04

forms

基础节点配置

基础节点配置为每个表单项都必定存在的配置。

注:这里为了介绍得更清晰,所以将其独立出来,在使用中基础节点配置与 inputselect 节点的对象要合并在一起。

  • label(支持多语言):表单项的名称(如果不增加 task_components 前缀,则中英文下都会显示为 node_name

  • type:表单项的类型,初版只支持 studioinputselect

  • field:提交表单时接口需要绑定的字段(如果存在接口入参格式是嵌套的情况 {a: {b: 1}}field 的值可写为 "a.b", 对应的结构解析器、校验解析器和值解析器会同步处理该情况。但是初版只支持两层的对象嵌套,且初版有嵌套对象的表单项为脚本项)

  • defaultValue:默认值, input 组件的默认值为 ""select 组件的默认值为 null

  • validate:表单项的校验规则

  • 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
    }
  ]
}
  • 动态

API:动态 options 需要调用的接口名称(需要在基础结构 apis 字段中做对应),并将请求后的数据覆盖到 selectoptions 字段中

{
  "api": "apiName"
}


04

解析器


为了能让表单结构化数据以浏览器能识别的 HTMLCSSJavaScript 进行渲染,为此,我们设计了一些解析器(由于是初版的原因,我们大体上对解析器做了一些设计,使它更加灵活的同时也为了方便后续的拓展)

注:由于多语言和表单校验是在组件的 setup 生命周期中进行注册和绑定,但是 vue3 的生命周期钩子最早的是 onBeforeMount 且该数据是异步接口获取的数据 。同时,解析器的代码重在展示逻辑,为此会使用 any 类型。

01

多语言解析器

多语言解析器用于解析基础结构中的 locales 字段,它会与本地的 vue-i18n 进行并集处理

useI18n().mergeLocaleMessage('zh_CN', { task_components: locales.zh_CN })
useI18n().mergeLocaleMessage('en_US', { task_components: locales.en_US })

02

值解析器

值解析器会将基础结构中 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 的值,否则会根据 inputselect 进行默认值)。

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 :
      ''
  )
}

03

校验解析器

用于解析基础结构中 forms 字段中的 validate 字段。

解析

解析并处理 validate 字段,在 validate 不存在或 validate 是空对象或 requiredfalsetype 不存在时,将不会处理当前表单项的校验逻辑。

注:校验解析器在处理嵌套 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 ,并将不会发生变化的 requiredtrigger 赋值进去。当该方法有 field 参数传递进来时代表当前表单项的 typestudio ,对于这个类型并不是原生的表单项,所以需要通过直解析器处理后的值进行判断。

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
}

04

请求解析器

用于动态请求 selectoptions 选项。

基础请求方法

区别于硬编码形式,动态请求我们会预留一个这样的基础请求方法,然后将参数传递进来,这样可以不改造项目集中封装的 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
}

05

结构解析器

结构解析器是 最后一个执行 的解析器,它的作用在于过滤掉 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

表单效果和配置数据


01

表单效果

表单的最终展现效果会根据配置数据进行渲染,修改表单信息只需要修改后端的 json 数据配置即可,而无需改动前端代码。

dbdf2eae04fd48458e4df1da544e856d.png

02

配置数据

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

结语


这就是 Apache DolphinScheduler 针对初版的动态任务组件的设计和实现方式。在未来,我们还会新增更多的表单种类,如单选、多选和开关等组件,也会有更多的交互逻辑的表达(比如可能会发布一套模板语法,用 lodashAST 进行解析),同样地,我们的解析器也会面临着诸多改造以支持更广泛的任务组件的需求。所以,如果读到这篇文章的你有 DolphinScheduler 动态任务组件实现 的更好的思路,也非常欢迎加入我们的贡献者种子群(联系人:  easyworkflow)与我们交流,  我们相信贡献者的力量!

参与贡献

随着国内开源的迅猛崛起,Apache DolphinScheduler 社区迎来蓬勃发展,为了做更好用、易用的调度,真诚欢迎热爱开源的伙伴加入到开源社区中来,为中国开源崛起献上一份自己的力量,让本土开源走向全球。

4e7e953a5a59a27da7365c9692be4cff.png

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

8aaa80a37115fd73dca76982f6984cf6.png

贡献第一个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) ,手把手教会您( 贡献者不分水平高低,有问必答,关键是有一颗愿意贡献的心 )。

e9d520edfa6b82a9a54ea7a000f5b47b.jpeg

添加社区小助手微信(Leonard-ds) 

添加小助手微信时请说明想参与贡献。

来吧,开源社区非常期待您的参与。

< 🐬🐬 >

更多精彩推荐

Apache DolphinScheduler 社区年度总结

☞Apache Dolphinscheduler 任务插件版图再添 Linkis,大幅提高计算治理能力

DolphinScheduler 快速构建 Hugging Face 文本分类工作流,基于工作流的机器学习训练部署太强了!

☞Apache DolphinScheduler 任务调度3.1.0版本源码剖析

☞名额已排到10月 | Apache DolphinScheduler Meetup分享嘉宾继续火热招募中

☞【Meetup讲师】您有一张社区认证讲师证书未领取,点击领取!

PyDolphinScheduler 4.0.0 正式发布

我知道你在看4d9d07d754be5321a4e8611f4143e35c.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DolphinScheduler社区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值