Erlang: как правильно диспетчировать gen_server с start_child в супервизоре и вызывать API
У меня есть gen_server
в моем приложении cavv, которое мне нужно сначала запустить, чтобы выполнить вызов. Я хочу использовать диспетчер команд для этого. Для краткого примера это gen_server
API:
gen_server: cavv_user
-module(cavv_user).
-behavior(gen_server).
-define(SERVER(UserId), {via, gproc, {n, l, {?MODULE, UserId}}}).
start_link(UserId) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId], []).
change_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER(AggregateId), {execute_command, #change_user_email_address{user_id=UserId, email_address=EmailAddress}}).
Прежде чем я могу позвонить cavv_user:change_email_address().
Мне нужно начать cavv_user
, Я делаю это как simple_one_for_one
ребенок в супервизоре, вот так:
руководитель: cavv_user_sup
-module(cavv_user_sup).
-behaviour(supervisor).
-define(CHILD(ChildName, Type, Args), {ChildName, {ChildName, start_link, Args}, temporary, 5000, Type, [ChildName]}).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child(UserId) ->
supervisor:start_child(?SERVER, [UserId]).
init([]) ->
RestartStrategy = {simple_one_for_one, 1, 5},
Children = [?CHILD(cavv_user, worker, [])],
{ok, { RestartStrategy, Children} }.
Проблема, с которой я сейчас сталкиваюсь, заключается в том, как cavv_user
, Я хочу убедиться, что первый пользователь запускается с помощью start_child
, а затем позвоните cavv_user:change_email_address().
Я нашел этот ответ, чтобы использовать диспетчер: Erlang: какое дерево контроля мне следует закончить написанием планировщика задач?
Поэтому я создал диспетчер команд и в итоге получил cavv_user_dispatcher
и cavv_user_dispatcher_sup
что в свою очередь содержит cavv_user_dispatcher
и чем раньше cavv_user_sup
:
cavv_user_dispatch_sup
| |
cavv_user_dispatcher |
(gen_server) |
|
|
cavv_user_sup
| | |
cavv_user_1...cavv_user_N
Cavv_user_dispatcher
Это прекрасно работает.
Проблема, с которой я сталкиваюсь сейчас, заключается в том, как правильно написать код в cavv_user_dispatcher
? Я столкнулся с проблемой с дублированием кода. Как правильно вызвать start_child и вызвать соответствующий API cavv_user?
Должен ли я использовать какой-то Fun
вот так?
-module(cavv_user_dispatcher).
dispatch_command(UserId, Fun) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun}}).
handle_call({dispatch_command, {UserId, Fun}}, _From, State) ->
cavv_user_sup:start_child(UserId),
Fun(), %% How to pass: cavv_user:change_email_address(..,..)?
{reply, ok, State};
Или продублируйте cavv_user
API как так?
-module(cavv_user_dispatcher).
change_user_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER, {change_user_email_address, {UserId, EmailAddress}}).
handle_call({change_user_email_address, {UserId, EmailAddress}}, _From, State) ->
cavv_user_sup:start_child(UserId),
cavv_user:change_email_address(UserId, EmailAddress),
{reply, ok, State};
Или я должен повторно использовать записи команд из cavv_user
в какую-то утилиту, чтобы правильно их построить и разослать? Может быть, лучший способ передать функцию, которую я хочу вызвать cavv_user
?
Я хотел бы решить эту проблему наилучшим образом, без дублирования кода.
1 ответ
Ваш диспетчер должен обрабатывать другие команды?
Если да, то как будет поступать следующая команда, я имею в виду, будет ли запрашивающая сторона знать pid процесса пользователя или нет?
если да, то вам нужны 2 функции: одна для создания пользователя, она вернет pid запрашивающей стороне для следующего вызова, а другая для обработки следующих запросов, отправив команду на указанный pid
если нет, то вам также нужны 2 функции: одна для создания пользователя и сохранения user_id вместе с pid пользовательского процесса, а другая для обработки следующего запроса путем получения pid процесса и последующей передачи ему команды (я полагаю, это то, что вы хотеть сделать).
если нет, то вам не нужно обрабатывать какие-либо команды, и вы должны передавать адрес электронной почты непосредственно при создании пользовательского процесса. Обратите внимание, что это верно для всех случаев, так как вам нужен другой интерфейс для создания пользователя.
Я бы изменил ваш код таким образом (не проверено, слишком поздно:o)!)
-module(cavv_user_dispatcher).
create_user(UserId,UserMail) ->
gen_server:call(?SERVER,{new_user,UserId,UserMail}).
% Args is a list of argument, empty if
% F needs only one argument (the user Pid)
dispatch_command(UserId, Fun, Args) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun,Args}}).
handle_call({dispatch_command, {UserId, Fun,Args}}, _From, State) ->
Pid = get_pid(UserId,State),
Answer = case Pid of
unknown_user_id -> unknown_user_id;
_ -> apply(Fun,[Pid|Args]),
ok
end,
{reply, Answer, State};
handle_call({new_user,UserId,UserMail},_From,State) ->
% verify that the user id does not already exists
CheckId = check_id(UserId,State),
{Answer,NewState} = case CheckId of
false -> {already_exist,State};
true -> {ok,Pid} = cavv_user_sup:start_child(UserId,UserMail)
{ok,[{UserId,Pid}|State]}
% State must be initialized as an empty list in the init function.
{reply, Answer, NewState};
...
get_pid(UserId,State) ->
proplists:get_value(UserId, State, unknown_user_id).
check_id(UserId,State) ->
not proplists:is_defined(UserId, State).
и супервизор пользователя должен быть изменен следующим образом:
start_child(UserId,UserMail) -> % change arity in the export
supervisor:start_child(?SERVER, [UserId,UserMail]).
а затем пользовательский сервер:
start_link(UserId,UserMail) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId,UserMail],[]).
init([UserId,UserMail]) ->
{ok,[{user_id,UserId},{user_mail,UserMail}]}.