incident如何使用Golang构建工作流程引擎?


在 incident.io,我们正在开发工具,以帮助人们应对事件,通常是通过自动化其组织的流程。

其中大部分是由我们的工作流程产品提供的,客户可以用它来实现以下目的。

  • 向执行团队发送有关重大事件的最新电子邮件
  • 只要事件是面向客户的,就上报给客户成功中心
  • 当一个事件结束时,Slack给领导发信息,提醒其安排汇报。

工作流作为一个产品功能是非常强大的,我们为它们为客户提供的价值感到自豪。

然而,在幕后,建立像工作流程这样的东西可能是困难的。

我们对我们如何建立我们的工作流引擎非常满意,这篇文章--分为两部分--分享了我们是如何做到的。

在第一部分中,我们首先解释了整个功能中使用的核心工作流概念,并查看了代码的结构,以使开发变得简单。随后,我们深入探讨了我们的工作流生成器(配置UI),展示了这些概念如何在API中暴露出来,并用于支持前端。

第二部分详细介绍了工作流执行器,展示了我们如何监听潜在的工作流触发器,如果条件符合,就执行它们。最后,我们在对项目的评估中反思了我们是否成功地实现了 "放慢速度,加速前进!"的努力。

核心工作流引擎
就其核心而言,工作流程可以被看作是这样一种实现。

当X发生时,如果Y,做Z

我们的工作流程实现将这一结构分成几个关键概念。

  • "当X发生时 "就是我们所说的触发器,其中一个触发器可能是 "一个事件已被创建"
  • "如果Y "是一组条件,适用于与触发器相关的细节,例如 "严重程度>关键"。
  • "做Z "是一个步骤列表,例如 "发送Slack消息 "或 "升级到PagerDuty"

客户从我们的仪表板工作流程生成器中配置工作流程,他们在那里配置触发器、其条件和任何要运行的步骤。


在高层次上,工作流的实施有两个方面:

  • 使用资源、条件和工作流程步骤建立工作流程配置
  • 响应触发器,执行工作流程

这两方面都有共同的执行组件,但我们将分别解释每一方面,在我们进行的过程中揭开共同部分。

核心工作流概念
在概述中,我们解释说,工作流就像计算机程序中的if条件。

我们所建立的工作流引擎和执行器最好正是这样想的:一种小而简单的编程语言,定制的目的是。

  • 轻松地表示工作流程与之互动的实体(一个事件或Slack频道)
  • 对这些实体进行静态分类,以帮助验证工作流程,并安全地发展其结构
  • 允许反映功能(步骤)和类型(资源),以支持工作流程生成器,它可以被视为工作流程语言的IDE。

这些目标可以从建立一个具有无代码前端的灵活的工作流引擎的具体挑战中得出,我们非常关心人们用我们创建的工作流的稳健性:毕竟,人们关心他们的事件过程是否可靠

在将工作流程比作一种编程语言时,我们引入了一些概念。

  • 资源Resource代表工作流程程序中可用的变量类型
  • 步骤Steps是接收实例化的资源并使用它们执行代码的函数。
  • 像许多编程语言一样,我们有一个scope范围的概念,它是一个命名的资源实例的集合,我们称之为 "引用"。
  • 触发器Trigger,它是可以触发工作流程的生命周期事件,建立一个范围,可以根据工作流程的条件进行检查,并向工作流程的步骤提供资源值

这些概念中的每一个都在我们的后台实现,并通过HTTP API暴露自己的信息,工作流生成器使用这些信息来驱动用户界面。在编程语言的类比中,你可以把工作流生成器看作是IDE。

代码结构
由于我们即将研究如何实现这些概念,因此了解后端代码的结构是非常有用的。

在可能的情况下,我们按类型(触发器、步骤、资源)对概念进行分组,其实现不需要知道任何其他类型:例如,任何资源实现都不会处理步骤或触发器,任何工作流步骤都不会知道触发器的概念。

这种分离有助于降低开发者常见任务的复杂性(例如,添加工作流步骤、触发器或资源),因为它不需要与更广泛的工作流系统交互。相反,开发人员实现了简单的构件,他们在系统的其他部分注册,使他们可以使用。

后台由Go语言编写,分为几个包。

  • pkg/engine,它提供了核心类型并实现了条件评估。
  • pkg/engine/resources,工作流可能与之交互的每个实体(Slack频道、事件、用户)都必须在这个包内实现engine.Resource接口
  • pkg/workflows,提供一个监听触发器并执行工作流的服务
  • pkg/workflows/triggers,所有的触发器(事件创建、更新等)必须实现一个触发器接口,并在此包内注册
  • pkg/workflows/steps,所有的步骤(发送Slack消息,升级到PagerDuty等)必须实现一个Step接口并注册。

包的结构清楚地反映了这种分离,每个 "东西"(无论是触发器、步骤还是资源)都在该包内的一个文件中实现。

同样值得注意的是,我们将工作流代码与底层引擎分开。这是因为引擎,包括它的资源和条件评估,并不是针对工作流的。

这种分离有助于澄清每个概念与其他概念的关系,并通过鼓励明确的界限,使我们能够在我们的应用程序中重复使用引擎结构,为条件性事件设置或自动存档事件的规则等提供动力。

详细点击标题