Приложение — целостная подсистема единой Системы, выполняющая определённый круг задач. То же: application.
Erlang/OTP можно мыслить как операционную систему в операционной системе (например, в Linux или Windows). Эрланг-программист может отвлечься от такой вещи как системный процесс и вместо него оперировать акторами. Данные в нашу Систему попадают разными путями, часто из файлов, но единожды попав в Систему, данные превращаются в термы и в таком виде циркулируют от актора к актору. Основным предметом работы программиста становятся акторы и термы. Новый и к тому же очень комфортный уровень абстракции, позволяющий делать всё что угодно, и позволяет рассматривать Erlang/OTP как операционную систему.
Сама по себе низкоуровневая операционная система (например, Linux или Windows) бесполезна для пользователя. Польза приходит тогда, когда в операционке запускают приложения. Аналогично, в Erlang/OTP основная польза появляется вместе с приложениями. Каждое приложение обладает внутренней целостностью, согласованностью, а также ценностью для пользователя (даёт некий важный функционал).
Тем не менее, можно в своей работе обойтись без написания приложений как таковых. Для каких-то своих целей можно подготовить модуль и функции в нём. Модуль — это ещё не приложение. Чтобы сделать именно приложение, потребуется ряд шагов.
Что же нам даёт приложение:
Чтобы понять принципы OTP относительно создания приложения, нам придётся разработать минимальное приложение, причём двигаясь последовательно.
Создание приложения начинается с задания модулю поведения application. Создадим пока модуль лишь с этими двумя строчками:
-module(my_app).
-behaviour(application).
Это, конечно вызовет у компилятора предупреждение, потому что любое поведение требует, чтобы были определены соответствующие колбэки.
my_app.erl:2:2: Warning: undefined callback function start/2 (behaviour 'application')
% 2| -behaviour(application).
% | ^
my_app.erl:2:2: Warning: undefined callback function stop/1 (behaviour 'application')
% 2| -behaviour(application).
% | ^
Как видим, не хватает функции start с арностью 2 и функции stop с арностью 1. Нетрудно догадаться, что эти колбэки нужны для запуска и остановки нашего приложения.
-module(my_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_A,_B) ->
ok.
stop(_A) ->
ok.
Мы пока не знаем, что там за аргументы нужны для start и stop, поэтому просто их обозначили как _A и _B. Обе функции возвращают пока атом ok. Их нам пришлось экспортировать, чтобы компилятор перестал ругаться. Теперь этот модуль компилируется.
Приложения запускаются с помощью функции application:start/1,2, а останавливаются с помощью application:stop/1. При этом будут запущены функции (колбэки) start/2 и stop/1 из нашего модуля my_app.
При запуске в качестве аргумента должен быть атом — название нашего модуля-приложения.
1> application:start(my_app).
{error,{"no such file or directory","my_app.app"}}
Оказывается, нужен ещё файл с расширением *.app, который описывает наше приложение. Без него приложение не запускается. Придётся искать в документации — какое минимальное содержание должно быть в этом так называемом ресурсном файле приложения.
И тут возможно три варианта. Если мы создаём лишь библиотечное приложение, минимально ресурсный файл должен выглядеть так:
{application, myapp, []}.
myapp — атом названия нашего приложения (для пробы я вместо my_app (название модуля) написал myapp, ведь название приложения может не совпадать с именем главного модуля приложения). В квадратных скобках могли бы быть дополнительные опции, но тут без них можно обойтись.
2-й вариант это приложение с древом надзора. В квадратные скобки ресурсного файла добавляется одна минимальная опция.
{application, myapp, [
{mod, {my_app,[]}}
]}.
Опция с ключом mod определяет базовый модуль (модуль с колбэками), с которого начинается запуск приложения. В квадратных скобках могут быть начальные аргументы при старте приложения. В данном случае их нет, и это означает, что приложение будет стартовать так:
my_app:start(normal, [])
Третий вариант — самый полный. Когда мы используем systools для упаковки кода в релиз, тогда потребуются ещё опции.
{application, myapp, [
{description, "My first application"},
{vsn, "0.01"},
{modules, [my_app, my_sup, my_worker]},
{registered, [my_worker]},
{applications, [kernel, stdlib]},
{mod, {my_app,[]}}
]}.
description — краткое описание приложения. vsn — номер версии приложения. modules — все модули, вводимые приложением. Поскольку в приложении потребуется модуль супервизии, я добавил в список модуль my_sup. Раз нужен хотя бы один работник, я добавил модуль my_worker. registered — те акторы, которые должны быть зарегистрированы в Системе. Я собираюсь работнику посылать задания, поэтому добавил здесь my_worker. applications — список зависимостей (других приложений). Как минимум везде требуются kernel и stdlib.
Опробуем все три варианта.
Создадим файл myapp.app, добавим в него первый вариант и запустим из оболочки.
1> application:start(myapp).
ok
2> application:stop(myapp).
=INFO REPORT==== 6-Sep-2025::08:41:21.588706 ===
application: myapp
exited: stopped
type: temporary
ok
Приложение запустилось и остановилось успешно.
Делаем второй вариант файла myapp.app.
1> application:start(myapp).
=INFO REPORT==== 6-Sep-2025::08:46:32.516203 ===
application: myapp
exited: {bad_return,{{my_app,start,[normal,[]]},ok}}
type: temporary
{error,{bad_return,{{my_app,start,[normal,[]]},ok}}}
=CRASH REPORT==== 6-Sep-2025::08:46:32.516534 ===
crasher:
...
Приложение рухнуло с ошибкой bad_return. Сразу ясно, что виновата функция my_app:start/2. У нас она возвращает атом ok, а должна — что-то другое. Придётся опять читать документацию.
Функция start должна иметь следующую сигнатуру:
start(StartType, StartArgs) -> {ok,Pid} | {ok,Pid,State}
Настало время создать модуль надзора (супервизии). Из функции my_app:start будет вызываться функция start_link модуля надзора. Создадим файл модуля my_sup и напишем пока опять только две строки.
-module(my_sup).
-behaviour(supervisor).
Компилятор на это ожидаемо ругается, что не хватает колбэков (точнее одного):
my_sup.erl:2:2: Warning: undefined callback function init/1 (behaviour 'supervisor')
% 2| -behaviour(supervisor).
% | ^
Находим в интернете пример init-функции и, пока особенно не думая, создаём недостающий колбэк. И, хотя нет такого требования поведения, создаём функцию start_link. Именно она запустит надзирателя, и при запуске как раз потребуется функция init.
-module(my_sup).
-behaviour(supervisor).
-export([init/1, start_link/0]).
init([]) ->
SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
ChildSpecs = [
#{id => my_worker,
start => {my_worker, start_link, []},
type => worker,
restart => permanent,
shutdown => 5000,
auto_shutdown => brutal_kill,
critical => true}
],
{ok, {SupFlags,ChildSpecs}}.
start_link() ->
supervisor:start_link({local,?MODULE}, ?MODULE, []).
Меняем и функцию my_app:start/2.
start(_A,_B) ->
my_sup:start_link().
Компилируем и запускаем. Получаем в общем-то ожидаемую ошибку:
1> application:start(myapp).
=SUPERVISOR REPORT==== 6-Sep-2025::11:00:52.450215 ===
supervisor: {local,my_sup}
errorContext: start_error
reason: {'EXIT',
{undef,
[{my_worker,start_link,[],[]},
...
Конечно, в модуле my_sup мы определили уже работника my_worker и что при старте у него должна выполняться функция start_link. Однако этого работника у нас ещё нет. Поэтому и получили ошибку undef, раз функция my_worker:start_link не найдена. Пора сделать работника.
Важный момент — работник должен обладать одним из следующих поведений: gen_event, gen_server или gen_statem. gen_event выглядит попроще, выберем его. У этого поведения должны быть три колбэка: init/1, handle_event/2 и handle_call/2. Плюс start_link, чтобы запустить надзор. Попробуем следующий вариант:
-module(my_worker).
-behaviour(gen_event).
-export([init/1, start_link/0, handle_event/2, handle_call/2]).
init(Args) ->
{ok, 0}.
start_link() ->
gen_event:start_link({local, ?MODULE}, []).
handle_event(Event, N) ->
io:format("*** some event:~p~n",[Event]),
{ok, N}.
handle_call(_Request, N) ->
Reply = N,
{ok, Reply, N}.
(Все поведенческие колбэки должны возвращать {ok, State}.)
Компилируем работника и запускаем приложение.
1> application:start(myapp).
ok
Ура! Наше приложение на этот раз запустилось. Смотрим список запущенных приложений и находим в нём своё:
2> application:which_applications().
[{myapp,[],[]},
{stdlib,"ERTS CXC 138 10","7.0.2"},
{kernel,"ERTS CXC 138 10","10.3.2"}]
Ради интереса можно запустить Обсервер:
3> observer:start().
ok
Так можно увидеть древо надзора.

Созданное нами приложение не только запускается, но и останавливается.
4> application:stop(myapp).
=INFO REPORT==== 6-Sep-2025::11:45:48.358534 ===
application: myapp
exited: stopped
type: temporary
ok
5> application:which_applications().
[{stdlib,"ERTS CXC 138 10","7.0.2"},
{kernel,"ERTS CXC 138 10","10.3.2"}]
Приложение создано, хотя как им пользоваться, пока не ясно.
Делаем 3-й вариант файла myapp.app. Приложение продолжает запускаться. Похоже, выше мы сделали всё необходимое и для этого варианта. systools нужен для работы с релизами, но нам пока об этом думать рано.
1> application:start(myapp).
ok
2> application:which_applications().
[{myapp,"My first application","0.01"},
{stdlib,"ERTS CXC 138 10","7.0.2"},
{kernel,"ERTS CXC 138 10","10.3.2"}]
Отличие только в том, что в списке работающих приложений у myapp появился краткий комментарий, взятый из ресурсного файла.
Copyright © 2025 Алексей Карманов