在主管中有逻辑可以吗?

Is it okay to have logic in a supervisor?

我目前正在研究 Elixir in Action 并且我正在重构我的 Todo 应用程序代码以更好地掌握 OTP 的主要部分。

该应用程序使用一个数据库,该数据库仅将数据存储在磁盘上的文件中。为确保数据库的目标文件夹存在,在数据库进程中调用 File.mkdir_p!(db_folder)。数据库进程本身使用一堆工作进程来执行磁盘中实际的 storing/retrieving 数据。

我目前所在的章节介绍了一个 DIY 进程注册表来实现更强大的监督树,方法是让工作人员将自己注册到注册表并让数据库进程使用注册表查找工作人员,这样双方都可以受监督,失败后仍会工作。

当 Elixir 1.4 发布时,我在补丁说明中读到了 Registry 模块,所以我想我可以重构应用程序并使用它。现在事实证明,数据库进程实际上不需要知道数据库用于存储数据的文件夹。所以我从那个模块中取出 mkdir_p! 调用并考虑将它放在哪里。我想到了两个选项:

  1. DatabaseWorker
  2. DatabaseWorkerSupervisor

我个人更喜欢第二种方法,因为如果用户没有持久性文件夹的访问权限,整个应用程序无论如何都会崩溃。但是我不太确定是否可以将逻辑放入 Supervisor 中。

根据情况,将逻辑放入 Supervisor 是糟糕的风格还是可以接受的?如果它是糟糕的风格,我应该在哪里放置我不想在进程崩溃时重复的启动逻辑?


我的主管代码:

defmodule Todo.DatabaseWorkerSupervisor do
  use Supervisor

  def start_link(db_folder) do
    Supervisor.start_link(__MODULE__, db_folder)
  end

  def init(db_folder) do
    File.mkdir_p!(db_folder)

    processes =
      for worker_id <- 1..3 do
        worker(Todo.DatabaseWorker, [db_folder, worker_id], id: {:dbworker, worker_id})
      end

    supervise(processes, strategy: :one_for_one)
  end
end

我宁愿不把逻辑放在监督者身上。如果你开始在监督者中加入逻辑,你就无法通过查看你的监督树来推断崩溃/重启。相反,我会建议以下监督树:

如果您将文件夹创建放在 DB 生成服务器的 init 中,DBSupervisor 将等待 init 到 return,然后再继续他的其他子级。因此,如果文件夹创建失败,监督树的其余部分甚至不会产生。此外,如果 DBSupervisor 策略是 :rest_for_all,DB 生成服务器中的任何故障都将重新启动其余的监督树。

我知道这个答案可能看起来有点矫枉过正,但如果强调纠正和学习,我认为这是正确的方向。

一个重要提示。正如您所说,通过注册,您实际上不需要 DB gen 服务器来将任务从客户端传递给工作人员,您是对的!尽管建议的监督树看起来与寄存器之前的树相似,但您现在应该只调用函数(可以在 DB 模块中实现)来查询寄存器并将任务直接从客户端进程传递给工作人员。

where do i put startup-logic that i do not want to be repeated if a process crashes?

从主管那里调用这个 init 似乎是合乎逻辑的地方。 特别是如果你不想重复它。

也许代码可以在另一个模块中定义,但是从supervisor init调用它是有意义的,如果初始化失败让supervisor崩溃。