Знайомство з 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/