作为 GenServers 的工作流
Workflows as GenServers
所以我偶尔会涉足 Elixir,但我过去一直在努力解决的问题始终是如何使用进程对应用程序进行建模。考虑到这一点,我想对我将要做的事情进行理智检查,看看它是否有意义。
所以我正在构建一个简单的工作流应用程序,每个阶段都有关于它如何流向下一阶段的规则,它可能收集的状态以及由于状态更改而触发的事件。阶段不是线性的,本质上是任何流程图中的图形。
我在查看流程图时的第一个想法是,它看起来非常像流程的集合。
我的想法是将流程中的每个阶段建模为自己的 GenServer,因为它们是可寻址的,可以保持状态并可用于执行事件。
如果这是一个明智的方法(如果不是,请告诉我)那么我有几个问题:
由于这是一个图表,每个阶段都可以有多个连接的下一个和上一个阶段。我希望这些阶段受到监督,现在我真的要在这里表明我缺乏理解,但我是否认为监督者只监控与之相关的进程,而不是子进程?如果是这种情况我有点希望每个阶段都充当 GenServer 和主管,这是 possible/sensible?
有没有办法获取一个进程的所有子进程?
这样合理吗?
克里斯
GenServers 绝对是您可以完成类似任务的一种方式,但是您在使用这种方式时必须解决一些实施问题。
所以 Elixir 已经为此提供了更高级别的实现,称为 GenStage,这是一件好事。您可以创建具有多个阶段的管道,其中每个阶段都可以充当消费者、生产者或两者。
您还可以在每个阶段生成更多的子进程,或者让这些阶段自己完成工作并使用 ConsumerSupervisor 来监督它们。
您还可以有多个阶段 运行 并行地将数据馈送到其他阶段,其中大部分实施由它自动处理。
这里有一些资源:
我认为进程树不需要和图有相同的拓扑。
我会做的是每个图只使用一个监督者,这个图中的所有进程都将由它监督。
使用Supervisor.which_children/1
获取一个Supervisor的所有子进程。
您可能想看看 Plug 构建 Phoenix 的行为是如何工作的。基本上,每个定义的函数(插件)都接受和 returns 一个结构,conn
,然后插件的管道链逐渐构建它 up/transform it/etc.
如果我对您的理解是正确的,这种模式会起作用 - 您最初构建一个 struct/map,然后您只需将其传递给一组逐渐转换它的函数即可。你也应该能够分支,它不需要是线性的。这样状态就保持在一个地方(你需要做的就是保持那个结构,你可以用一个单独的 genserver 来做到这一点)。
虽然这样做很诱人,但我会认真避免将每个步骤建模为 genserver:这是滥用它们来尝试模拟对象。使用仅重建状态的普通函数将更简单、更易于调试且更符合习惯(this article 更详细地解释了何时使用进程以及何时不使用进程,以及为什么)。
所以我偶尔会涉足 Elixir,但我过去一直在努力解决的问题始终是如何使用进程对应用程序进行建模。考虑到这一点,我想对我将要做的事情进行理智检查,看看它是否有意义。
所以我正在构建一个简单的工作流应用程序,每个阶段都有关于它如何流向下一阶段的规则,它可能收集的状态以及由于状态更改而触发的事件。阶段不是线性的,本质上是任何流程图中的图形。
我在查看流程图时的第一个想法是,它看起来非常像流程的集合。
我的想法是将流程中的每个阶段建模为自己的 GenServer,因为它们是可寻址的,可以保持状态并可用于执行事件。
如果这是一个明智的方法(如果不是,请告诉我)那么我有几个问题:
由于这是一个图表,每个阶段都可以有多个连接的下一个和上一个阶段。我希望这些阶段受到监督,现在我真的要在这里表明我缺乏理解,但我是否认为监督者只监控与之相关的进程,而不是子进程?如果是这种情况我有点希望每个阶段都充当 GenServer 和主管,这是 possible/sensible?
有没有办法获取一个进程的所有子进程?
这样合理吗?
克里斯
GenServers 绝对是您可以完成类似任务的一种方式,但是您在使用这种方式时必须解决一些实施问题。
所以 Elixir 已经为此提供了更高级别的实现,称为 GenStage,这是一件好事。您可以创建具有多个阶段的管道,其中每个阶段都可以充当消费者、生产者或两者。
您还可以在每个阶段生成更多的子进程,或者让这些阶段自己完成工作并使用 ConsumerSupervisor 来监督它们。
您还可以有多个阶段 运行 并行地将数据馈送到其他阶段,其中大部分实施由它自动处理。
这里有一些资源:
我认为进程树不需要和图有相同的拓扑。
我会做的是每个图只使用一个监督者,这个图中的所有进程都将由它监督。
使用Supervisor.which_children/1
获取一个Supervisor的所有子进程。
您可能想看看 Plug 构建 Phoenix 的行为是如何工作的。基本上,每个定义的函数(插件)都接受和 returns 一个结构,conn
,然后插件的管道链逐渐构建它 up/transform it/etc.
如果我对您的理解是正确的,这种模式会起作用 - 您最初构建一个 struct/map,然后您只需将其传递给一组逐渐转换它的函数即可。你也应该能够分支,它不需要是线性的。这样状态就保持在一个地方(你需要做的就是保持那个结构,你可以用一个单独的 genserver 来做到这一点)。
虽然这样做很诱人,但我会认真避免将每个步骤建模为 genserver:这是滥用它们来尝试模拟对象。使用仅重建状态的普通函数将更简单、更易于调试且更符合习惯(this article 更详细地解释了何时使用进程以及何时不使用进程,以及为什么)。