Comments 12
Но реализовать на уровне application:start и application:stop тоже можно.
Ну проще оно может и проще, но правильнее — вряд-ли по множеству причин. Например если понадобится эту ets-ку привязать к супервизору второго уровня типа
top_level_sup
-- some_worker
-- sub_sup + ets
---- some_sub_workers
То сделав потом во время работы приложения supervisor:terminate_child(top_level_sup, sub_sup)
мы грохнем ets-ку не сделав дамп.
Я почему-то подумал, что вы предлагали создавать ETS не в процессе application controller, а в процессе корневого супервизора приложения. Ну тогда этот аргумент, пожалуй, не сработает.
Но я бы в любом случае 10 раз подумал бы прежде чем пристёгивать ETS к "системному" процессу. Часто потом возникает необходимость вокруг этой ETS наворачивать дополнительную логику, периодические всякие таски…
Опять же, всегда имеет смысл заворачивать ETS в модуль с API функциями вместо прямого обращения через ets:*
, а для этого имеет смысл создать отдельный модуль. А если уже модуль создал, то конвертнуть его в gen_server это 10 строк.
работать с ets полностью через него
Имеете в виду работать с приватной таблицей через gen_server:call
или работать с публичной таблицей через ets:*
, но завернуть их в API-функции?
Спасибо за статью.
Небольшой код-ревью сделаю, если уместно:
- https://github.com/Vonmo/acounter/tree/b69c0904ed43838fd88abf361fa3bbae26098066/apps/metrix почему решили писать свою библиотеку для метрик? Если для тренировки то ок, а так есть folsom или prometheus.erl
- https://github.com/Vonmo/acounter/blob/b69c0904ed43838fd88abf361fa3bbae26098066/apps/acounter/src/counter_handler.erl#L18-L24 вам этот паттерн не кажется повторяющимся? Как по мне стоило бы переписать как
init(Req, State) -> Method = proplists:get_value(m, State, undef), {Status, Body} = handle(Method, Req, State), Req2 = cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, jiffy:encode(Body), Req), metrix:increase_counter("web.requests.http." ++ atom_to_list(Method)), {ok, Req2, State}. handle(...) ->
- https://github.com/Vonmo/acounter/blob/b69c0904ed43838fd88abf361fa3bbae26098066/apps/acounter/src/counter_handler.erl#L23 и все остальные случаи вызова
ets:*
из этого модуля, при том что сама ets таблица создаётся в другом модуле — это очень плохая практика, т.к. раскрывает детали реализации и создает путаницу. Вам даже макрос?TABLE_NAME
из за этого в 2х местах пришлось задавать. Стоит для каждой операции создать функцию в модулеcounters_storage.erl
(increase/1
,decrease/1
,get/1
,reset/1
) и из других модулей вызывать уже эти обёртки. - https://github.com/Vonmo/acounter/blob/b69c0904ed43838fd88abf361fa3bbae26098066/apps/acounter/src/counters_storage.erl#L55 ошибки tab2file / file2tab стоит всё-же обрабатывать явно, как минимум логгировать. Вы из за этого не заметили что у вас ошибка во втором аргументе file2tab. Поюс после успешного file2tab последующий вызов
ets:new
с тем же именем таблицы просто бросит исключение (т.к. имя уже занято)
Насчет того что вам советуют создавать ETS прямо из супервизора — плохой совет. Для быстрых хаков ещё куда ни шло, но для реальных проектов и обучающих статей — плохая практика. Во первых ваши dump/restore не получилось бы тогда реализовать корректно, во вторых если захочется добавить какие-то периодические операции типа "удалять устаревшие ключи раз в 10 минут", то всё равно аккуратнее всего это будет смотреться внутри одного модуля с gen_server.
1) Метрики в таком виде были написаны в далеком 2011 году. Сначала просто, как индикатор, т.е открываем tail -f metrics_file и смотрим счетчики, а потом в некоторых проектах эти файлы обрабатывались на узлах агентами и отрпавлялись в zabbix. В статью импортировал этот код для того, чтобы показать, как работает метапроект из нескольких приложений.
Если выбирать из существующих библиотек, то folsom или exometer вполне решают данную задачу.
Так же хочется отметить, что в примере приложения нет логгера. Про подходы к логгированию и анализу логов тоже можно написать статью. В качестве практики для интересующихся, можно попробовать прикрутить lager (c вынесением в заголовочный файл макросов с уровнями от DEBUG до FATAL).
2) Убрал повторяющийся паттерн. Только добавил Req и State к Status, Body. Так как в обработчике могут поставить например cookie. Ну и State необходим, например для долгоживущих обработчиков (websocket, http-streaming)
3) Согласен, как раз в статье про внешние хранилища данных этот код модернизируется, и детали реализации хранилища спрятаны в общем интерфейсе: сначала добавляется класс обслуживания основанный на tarantool, а затем класс обслуживания на riak. Поправил интерфейс.
4) Catch убрал из реализации. Действительно за ним не было видно проблемы. Catch в erlang не стоит использовать, только совсем уж в крайних случаях.
Исходную проблему можно было отловить с помощью дополнительного теста проверяющего, что данные восстановились.
Готовим рабочее окружение для Erlang проекта