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}]}.
Другие вопросы по тегам