如何对Cadence工作流程进行版本控制?

时间:2020-04-19 01:23:11

标签: cadence-workflow

Cadence工作流必须是确定性的,这意味着如果使用相同的输入参数执行工作流,则预期会产生完全相同的结果。

当我以新的Cadence用户身份了解了以上要求时,我想知道当需要确定性突破性的更改时,如何长期维护工作流程。

一个示例场景是,您有一个工作流连续执行Activity1和Activity2,然后您需要更改这些活动的顺序,以便工作流在Activtiy1之前执行Activity2。还有许多其他方法可以进行类似确定性的更改,我想了解如何处理这些更改。

在工作流可以长时间运行(例如几天,几周甚至几个月)的情况下,这尤其重要!

1 个答案:

答案 0 :(得分:1)

显然,这可能是新的Cadence开发人员提出的最常见问题之一。 Cadence工作流程必须为deterministic algorithms。如果工作流算法不确定,则Cadence工作人员在尝试重播历史记录时(例如,在工作人员故障恢复期间)将有遇到不确定工作流错误的风险。

有两种方法可以解决此问题:

  • 创建全新的工作流程:这是最幼稚的方法 版本控制工作流程。这种方法听起来很简单:随时随地 您需要更改工作流程的算法, 原始工作流程的副本,并按您想要的方式对其进行编辑,然后赋予它 一个新名称,例如MyWorkflow_V2,并开始用于所有新实例 往前走。如果您的工作寿命不是很长, 现有工作流程将在某个时候“耗尽”,您将能够 完全删除旧版本。另一方面,这 方法可能很快变成维护噩梦 明显的原因。
  • 使用GetVersion()API派生工作流逻辑:Cadence客户端具有 一个名为GetVersion的函数,它会告诉您 工作流当前正在运行。您可以使用返回的信息 通过此功能来确定您的工作流程算法的版本 需要使用。换句话说,您的工作流程既有旧的 新算法并排运行,您可以选择 适用于您的工作流程实例的正确版本,以确保它们运行 确定性地。

下面是基于GetVersion()的方法的示例。假设您要在工作流程中更改以下行:

err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)

err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)

这是一项重大更改,因为它运行 bar 活动而不是 foo 。如果您只是进行更改而不必担心确定性,那么您的工作流将无法重播,并且会出现不确定性工作流错误。正确进行此更改的正确方法是按如下方式更新工作流程:

v :=  GetVersion(ctx, "fooChange", DefaultVersion, 1)
if v  == DefaultVersion {
   err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
} else {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
}

GetVersion函数接受4个参数:

  • ctx 是标准上下文对象
  • “ fooChange” 是人类可读的ChangeID或您在工作流程算法中进行的语义更改,破坏了确定性
  • DefaultVersion 是一个常量,仅表示 Version 0 。 的话,第一个版本。它作为 minSupportedVersion 传递 GetVersion函数的参数
  • 1 maxSupportedVersion ,可以由当前版本处理 工作流程代码。在这种情况下,我们的算法可以支持工作流程 从 DefaultVersion Version 1 (含)的版本。

当此工作流程的新实例首次到达上面的GetVersion()调用时,该函数将返回 maxSupportedVersion 参数,以便您可以运行最新版本的工作流程算法。同时,它还将在工作流程历史记录中记录该版本号(内部称为“ 标记事件”),以便将来使用。稍后重播此工作流时,即使您传递了不同的 maxSupportedVersion 参数(即,如果您的工作流有更多版本),Cadence客户端也将继续返回相同的版本号。

如果在历史记录重播期间遇到GetVersion调用,并且该历史记录没有先前记录的标记事件,则该函数将返回 DefaultVersion ,并假定“ fooChange” 在此工作流实例的上下文中从未存在。

如果您需要在工作流的同一步骤中再做一次重大更改,只需更改上面的代码,如下所示:

v :=  GetVersion(ctx, "fooChange", DefaultVersion, 2) // Note the new max version
if v  == DefaultVersion {
   err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
} else if v == 1 {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
} else { // This is the Version 2 logic
   err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
}

如果您愿意放弃对版本0 的支持,则可以像上面那样更改上面的代码:

v :=  GetVersion(ctx, "fooChange", 1, 2) // DefaultVersion is no longer supported
if v == 1 {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
} else { 
   err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
}

此更改之后,如果您的工作流代码针对具有 DefaultVersion 版本的旧工作流实例运行,则Cadence客户端将引发错误并停止执行。

最终,您可能希望摆脱所有以前的版本,而仅支持最新版本。一种选择是简单地完全摆脱GetVersion调用和if语句,而只用一行代码来完成正确的事情。但是,将GetVersion()调用保留在其中实际上是一个更好的主意,这有两个原因:

  1. GetVersion()使您可以更好地了解如果您遇到了什么问题, 工作人员尝试重播旧工作流程实例的历史记录。 而不是调查神秘事物的根本原因 不确定的工作流程错误,您将知道失败是 是由于此位置的工作流版本控制所致。
  2. 如果您需要对同一步骤进行更多重大更改 工作流程算法,您将能够重复使用相同的更改ID和 继续遵循与上面相同的模式。

考虑到上述两个原因,当您应该放弃对所有旧版本的支持时,您应该像下面那样更新工作流代码:

GetVersion(ctx, "fooChange", 2, 2) // This acts like an assertion to give you a proper error
err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)