现代凤凰网络应用程序中的OTP和Ecto代码分离

时间:2018-01-16 16:48:40

标签: elixir phoenix-framework ecto otp decoupling

在观看了this谈话之后,我了解了如何分离Web界面和OTP应用程序,但是OTP应用程序和Ecto代码应该如何分开呢?

目前我正在编写一个OTP应用程序,它调用Ecto函数或Ecto函数的包装函数, 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回复是使用以前生成的特定于用户的数据计算的 - 我希望应用程序能够在重新启动后继续运行。

1 个答案:

答案 0 :(得分:3)

您的代码示例根本不会触及GenServer状态,这可能意味着它首先不需要在GenServer中。

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

此处的一般准则是不使用进程进行代码组织,而是在需要表达某些运行时属性时使用,例如并发,全局状态或容错。

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

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

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