
1. Преамбула
В литературе по программированию считается хорошим тоном начать демонстрацию программных средств с примитивной программы, выводящей на экран фразу "Hello, World!".
В разработке системы взаимодействующих движков на Elixir, о которой я писал в статье https://habr.com/ru/articles/1002748/, я как раз подошёл к вопросу отображения поступающих данных телеметрии на экран. Когда были готовы соответствующие базовые модули, я воодушевился идеей повторить знаменитый пример из учебника Кернигана и Ритчи. В результате у меня получилось следующее.
2. Замысел
Сначала я придумал приём исключить из слоеной "запеканки вермишели", т.е. связей, объединяющих движки в рабочую сеть, сами... движки. В результате остались только снизу "запечённый" слой генераторов данных (не путать с тактовыми генераторами, о которых писалось ранее) и "верхняя корочка" так называемых стоков.
Термин сток заимствован из событийно-ориентированная архитектуры (EDA). Если угодно, то по-русски это будут выходные отверстий, куда данные "утекают". Напоминаю, что мы разрабатываем систему потоковой обработки данных, где данные находятся постоянно в движении.
3. Систематизация аппаратных средств
Коротко говоря, все довольно банально — концевые узлы графа потока данных каким–то образом завязаны на аппаратные периферийные средства сверх и снизу рабочего слоя движков. Поэтому предварительно необходимо провести какую–то систематизацию аппаратных средств. Разумно допустить, что на условном нижнем слое располагаются приборы следующих классов:
• клавиатура, как точка ввода алфавитно-цифровой информации настроек системы,
• драйвер шины регулярно поступающих данных, например, шина Modbus,
• адаптеры спонтанно поступающих данных, например, Can-шина.
Примечание. Прошу моих читателей принять моё своеобразное видение вычислительной системы, которое мне очень близко. В качестве знакомого примера такого представления могу назвать обычный структурный граф PID–регулятора.
Многие работали с промышленной шиной Modbus. Ну, а с Can-шиной мы сталкиваемся постоянно в современных автомобилях, которые напичканы под завязку датчиками и адаптерами Can-шины. Когда мастер подключает свой девайс с дисплеем к автомобилю, то он подключается к ЦБУ Can-шины.
Я ввел классификацию обозначенных шин как R_gateway (регулярный шлюз) и Ir_gateway (иррегулярный). Уже после реализации имитаторов шин я увидел, что между ними небольшая разница. Может быть, в дальней регулярный шлюз потребует дополнительной работы по селекции и коммутированию данных в отдельные каналы.
С клавиатурой, надеюсь, все понятно. В системах управлениях клавиатуры используются в основном для ввода значений уставок и соответствующие узлы получили классификационное название Setpoint.
В условном верхнем слое графа потока данных находятся потребители данных:
• отображающий дисплей,
• файловая система, например, для журналирования событий и сбоев,
• БД, как промежуточный накопитель. или как сейчас принято говорить, репозиторий
• и т.д.
На этом списке наша фантазия не ограничивается. Например, это может быть система более высокого уровня. Если это так, то из–за симметрии: что сверху, то и снизу, — системы движков могут собираться в гирлянды через шлюзовые узлы. Но этот вопрос нуждается отдельного изучения и обсуждения…
Все узлы верхнего уровня отнесены к классу Stock.
Пока что для имитации потоков данных с нижнего уровня на верхний названных классов достаточно. В результате систематизации получился следующий граф:

В нотации узлов раскрывается часть их состояния: первый параметр – конкретное название узла, второй — список получателей данных. У узла :display список получателей пустой, что естественно.
4. Реализация
Выше рассмотренные узлы я решил реализовывать максимально просто без обращения к шаблону GenServer OTP с его поддержкой от Supervosor.
Это очень простые процессы с зацикленной функций loop. Может быть, я не прав, но там нечему ломаться. C другой стороны, я бы предпочёл обработку ошибок, не на основе стратегии «Let it crash», а на осмысленной диспетчеризации событий в сети.
Были реализованы 3 узла:
• модуль ввода данных с терминала,
• программный имитатор источника регулярно поступающих данных,
• программный имитатор источника спонтанно поступающих данных,
замкнутые на 4–ый выходной узел: терминал вывода алфавитно-цифровой информации на экран.
Промежуточный слой движков остался пустым, как и планировалось.
Наверное, про данную реализацию больше добавить нечего. Его код после добавления рабочих движков будет выложен на GitHub.
Результат работы 4-х узлов показан на заставке. Теоретически вывод алфавитно-цифровой информации на экран будет бесконечным.
Имитатор регулярных данных бесконечно посылает в поток реплику "Hello, " с периодичностью в 1 сек. Имитатор спонтанных данных бесконечно посылает в поток реплику "World! " с периодичностью в пределах до 1 сек. А вот с терминала фразу "Привет, Мир!" я вводил сам вручную.
5. Попутно выполненная работа
Распределил систему на специализированные терминалы.
В этом вопросе были свои трудности. Требовалось сделать терминальный ввод/вывод, привязанным к локальному процессу, а штатно Erlang производит сбор ввода/вывода со всех узлов на один терминал. Это сделано специально для управления из центра.
Почему–то этот вопрос на сайтах обсуждается вскользь по минимуму. Чтобы найти решение в виде оператора:
:global.register_name(:stock_ldr, :erlang.group_leader) (см. на заставке)
я потратил два дня.
Создан центрального диспетчера для загрузки и запуска системы в работу.
Вот что говорится по этому поводу в Интернете [1]:
«Наша цель должна заключаться в создании единого внутреннего модуля, отвечающего за взаимодействие с внешней зависимостью.
Для достижения слабой связанности в нашей системе мы можем использовать гексагональную архитектуру, то есть, наша цель — вынести все внешние зависимости на периферию приложения, отделив основную бизнес-логику от некоторых побочных эффектов. Обычно это реализуется путем обертывания внешних библиотек (зависимостей) и использования только этих модулей-оберток в остальной части кодовой базы. Хорошим эмпирическим правилом будет наличие только одного модуля, представляющего внешнюю зависимость, будь то API или база данных.»
Что такое гексагональная архитектура я не знаю, но интуитивно стремился именно к достижению слабой связности с внешними модулями: хотел не описывать их в зависимостях. И честно говоря, я в этом не преуспел. Может, вы знаете решение?
Еще меня смущает в [1] то, что автор говорит о «наличии в коде только одно модуля, представляющего внешнюю зависимость.» У нас вырисовывается их масса.
6. Умозаключение
Проектируемая система взаимодействующих движков априори является открытой для расширения.
В архитектуре системы любой концевой узел графа является шлюзом (gateway), окном в примыкающую область. Это средства интеграции с внешним миром. Внешние пользовательские специализированные (custom) программные компоненты необходимо свободно подгружать в систему. Таким образом, система движков развивается.
Одно замечание, которое может изменить точку зрения на стереотипные вещи: система движков растет не «в верх», а прорастает в прикладные области «в низ». Если у вас есть мысли по этому поводу, милости прошу высказаться в комментах.
7. Благодарность
Я благодарен тем ~10% читателей, которые дочитывают мои статьи на Хабре до конца. Вы молодцы! Я надеюсь, что в конце разработки вы не разочаруетесь в рассматриваемых концепциях.
Литература:
1. Рекомендации по интеграции со сторонними библиотеками в Elixir, https://dev.to/jackmarchant/best-practices-for-integrating-with-third-party-libraries-in-elixir--59ff
