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