在观看了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
回复是使用以前生成的特定于用户的数据计算的 - 我希望应用程序能够在重新启动后继续运行。
答案 0 :(得分:3)
您的代码示例根本不会触及GenServer状态,这可能意味着它首先不需要在GenServer中。
实际上,将它放在GenServer中可能是一个非常糟糕的主意,因为您可能将所有数据库操作放在一个进程后面,这将成为系统中的瓶颈。
此处的一般准则是不使用进程进行代码组织,而是在需要表达某些运行时属性时使用,例如并发,全局状态或容错。
要更准确地回答您的问题,请将您的域API视为常规模块和功能,这可能需要与许多流程进行通信才能完成工作。这些过程越小,越集中,代码通常就越清晰。如果您需要一个进程来保持状态,请关注其状态,而不是直接向其添加业务逻辑。如果您需要一个进程来充当锁,请单独实现锁,与您的用例和域分离。等等。
Spawn但不是Spawn的文章可能会有所帮助。我是合着者的Adopting Elixir book也探讨了这些主题。
编辑:特别是对于您的示例,您可以将上面的所有代码移到名为initial_workout/3
的单个函数中,该函数接收user_id
,maxreps
和goal
作为参数并完全绕过GenServer。