Знайомство з Erlang [12]

Розглянемо як працюють gen_server та supervisor на прикладі реальної задачі

допустимо ми хочемо періодично виконувати певні дії у нашому ерланг-додатку
для прикладу, на сайті користувач заповняє в анкеті поле "ваше місто",

ми користуємось "google places api web service" для автодоповнення-пошуку міст,
користувач зберігає знайдене місто у вигляді id та city_name вибраною ним мовою
(для прикладу, українською)

ми хочемо показувати користувачам на сайті назви міст вибраною ними мовою
(не лише українською, а ще й російською, англійською etc)

на способі зберігання міст в СУБД та роботою з api зараз не будемо зупинятись,
нас цікавить робота cron-like erlang_worker,
тому напишу лише url для роботи з вищезгаданим гугл-сервісом -

Url = "https://maps.googleapis.com/maps/api/place/details/json?placeid=" ++ Place_Id ++ "&key=" ++ Api_Key ++ "&language=" ++ Lang,

для початку, нам потрібно прописати налаштування запуску процесів дерева супервізорів нашого n2o-додатку
тому в https://github.com/221V/ublog/blob/master/apps/ublog/src/ublog.erl#L13
замість

{ok, {{one_for_one, 5, 10}, [spec()]}}.

ми напишемо

{ok, {{one_for_one, 5, 10}, [
  spec(),
  #{id => worker_2,
    start => {google_places_worker, start_link, []},
    restart => permanent,
    type => worker,
    modules => [google_places_worker] }
]}}.

детальніше про дерево супервізорів ви можете прочитати тут -
https://github.com/yzh44yzh/practical_erlang/blob/master/12_supervisor/lesson_12.md
там же ви можете ознайомитись з поясненням параметрів

як випливає з налаштувань вище, наш модуль з gen_server має назву google_places_worker
для початку розглянемо які бувають коллбеки в gen_server -

gen_server:call - виклик

gen_server:call(my_module, {get, "abc"}),

в модулі

handle_call({get, Key}, _From, State) ->
  {reply, map:get(Key, State), State};

get(Key) -> gen_server:call(?MODULE, {get, Key}).

gen_server:cast - виклик

gen_server:cast(my_module, {set, "key1", "value1"}).

в модулі

handle_cast({set, Key, Value}, State) ->
  {noreply, map:put(Key, Value, State)};
handle_cast(_Req, State) ->
  {noreply, State}.

handle_info - виклик

my_module ! 777.
my_module ! {777, qwetrr}.
erlang:send_after(1000, self(), process).
self() ! 777.

в модулі

handle_info(process, State) ->
 ... .

пояснення --
handle_call -- для виклику ззовні модуля і отримання результату одразу (синхронно)
handle_cast -- асинхронне повідомлення, коли нам не важливий результат (результату не повертає)
handle_info -- як handle_cast, тільки відправляємо довільне повідомлення

загалом це головні коллбеки gen_server,
про інші ви можете прочитати тут -
https://github.com/yzh44yzh/practical_erlang/blob/master/10_gen_server_2/lesson_10.md
https://habr.com/ru/post/55708/
https://erlang.org/doc/man/gen_server.html

і на завершення - код нашого ерланг-воркера -

-module(google_places_worker).
-behaviour(gen_server).

-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start_link() ->
  gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init(_) ->
  erlang:send_after(1000, self(), process),
  {ok, []}.

handle_call(_Req, _From, State) ->
  {reply, not_handled, State}.

handle_cast(_Req, State) ->
  {noreply, State}.

handle_info(process, State) ->
  % тут ми робимо запит до нашої субд,
  % дістаємо записи з якими потрібно працювати,
  % працюємо з ними - стукаємо до апі гугл сервісу,
  % зберігаємо отримані від гугл сервісу значення

  erlang:send_after(5000, self(), process),
  {noreply, State}.

terminate(Reason, _State) ->
  io:format("Daemon ~p terminating: ~p~n", [?MODULE, Reason]),
  ok.

code_change(_OldVsn, State, _Extra) -> 
  {ok, State}.

Продовження

Посилання

https://erlang.org/doc/man/gen_server.html
https://github.com/yzh44yzh/practical_erlang/blob/master/10_gen_server_2/lesson_10.md
https://github.com/yzh44yzh/practical_erlang/blob/master/12_supervisor/lesson_12.md
https://habr.com/ru/post/55708/