【问题标题】:OTP and Ecto code separation in a modern Phoenix web app现代 Phoenix Web 应用程序中的 OTP 和 Ecto 代码分离
【发布时间】:2018-06-25 10:45:23
【问题描述】:

看了this的演讲,我明白了如何分离Web界面和OTP应用程序,但是如果有的话,应该如何分离OTP应用程序和Ecto代码呢?

目前我正在编写一个调用 Ecto 函数或 Ecto 函数的包装函数的 OTP 应用程序,handle_call/3 回调中:

@doc """
Generates a workout.
iex> Pullapi.Database.delete_workouts()
iex> Pullapi.Database.delete_sets()
iex> result = Pullapi.GenServerWorker.handle_call({:initial_workout, 1, 20, 25}, nil, %{})
iex> {:reply, [{:ok, %Pullapi.Set{__meta__: _, action: action, id: _, inserted_at: _, order: _, units: _, updated_at: _}}| rest], %{}} = result
iex> action
"Pullups"
"""
def handle_call({:initial_workout, user_id, maxreps, goal}, _from, state) do
  # insert Goal
  %Pullapi.Goal{user_id: user_id, units: goal}
  |> Pullapi.Database.insert_if_not_exists


  # get Inital config
  config = Application.get_env(:pullapi, Initial)

  # retrieve id from inserted Workout row
  result = %Pullapi.Workout{user_id: user_id} |> Pullapi.Database.insert_if_not_exists

  case result do
    {:ok, workout} ->
        %Pullapi.Workout{__meta__: _, id: workout_id, inserted_at: _, updated_at: _, user_id: _} = workout

        inserted_sets = maxreps
        |> (&(&1*config[:max_reps_percent]/100  |> max(1))).()
        |> round
        |> Pullapi.Numbers.gaussian(
             config[:standard_deviation],
             config[:cap_percent],
             config[:cut]
           )
        |> Pullapi.Database.make_pullup_sets(workout_id)
        |> Pullapi.Database.add_rest_sets(config[:rest_intervals])
        |> Enum.map(&Pullapi.Repo.insert/1)

    {:error, _}  ->
        inserted_sets = []
  end

  {:reply, inserted_sets, state}
end

这种方法是否将两者结合得太紧密了?

使用了数据库,因为 GenServer 回复是使用先前生成的用户特定数据计算得出的 - 我希望应用在重新启动后仍然存在。

【问题讨论】:

  • 这可能是一个过于宽泛的问题,包括示例将有助于回答它。请注意,仅出于包装 Ecto 调用和组织代码的目的而创建 GenServer 是不可以的。这篇文章可能会提供一些指导:theerlangelist.com/article/spawn_or_not
  • 我们还在 Adopting Elixir 书中介绍了类似的主题:pragprog.com/book/tvmelixir/adopting-elixir(免责声明:我是作者之一)。
  • 谢谢,我添加了一个例子。也会阅读这篇文章。

标签: elixir phoenix-framework ecto erlang-otp decoupling


【解决方案1】:

您的代码示例根本不涉及 GenServer 状态,这可能意味着它首先不需要位于 GenServer 内部。

实际上,将它放在 GenServer 中可能是一个非常糟糕的主意,因为您可能会将所有数据库操作放在一个进程之后,这现在将成为您系统的瓶颈。

这里的一般准则是不要将进程用于代码组织目的,而是用于当您需要表达一些运行时属性时,例如并发、全局状态或容错。

要更准确地回答您的问题,请将您的域 API 视为常规模块和函数,它们可能需要与许多进程对话才能完成工作。这些流程越小、越集中,代码通常就越干净。如果您需要一个流程来保持状态,请关注其状态,而不是直接向其添加业务逻辑。如果您需要一个进程充当锁,请单独实施锁,与您的用例和域分离。等等等等。

Spawn 但不是 Spawn 文章可能会有所帮助。我是合著者的Adopting Elixir book 也探讨了这些主题。

编辑:特别是对于您的示例,您可以将上面的所有代码移动到一个名为 initial_workout/3 的函数中,该函数接收 user_idmaxrepsgoal 作为参数并完全绕过 GenServer。

【讨论】:

  • 谢谢,这很有启发性 - 我在这里使用 GenServer 的动机是通过 API 公开应用程序逻辑 - 但是,如前所述,我认为使用常规函数的 API 也可以工作没有任何瓶颈,Phoenix/cowboy Web 界面处理请求负载。
  • 是的,我什至会说通过 GenServer 隐藏应用程序逻辑是一种反模式。
猜你喜欢
  • 2010-09-21
  • 2013-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 2015-05-16
相关资源
最近更新 更多