Не так давно мне на просторах Хабра попалась статья-начало цикла о терминалах самообслуживания, в которой было рассказано из каких «железяк» может состоять терминал. Что и напомнило мне, что когда-то давно некоторая группа людей, в которую входил и я, пыталась покорить вселенную своим стартапом, как раз связанным с терминалами и т. д. Но вселенная посмотрела на нас, почесала где-то в области затылка и поняла, что ей и без завоевателей не плохо живется, т. е. как вы поняли, стартап не взлетел. Но некоторый опыт-то остался. Как раз про терминальный софт я и хотел изложить свое мнение. Может оно и будет полезным иным покорителям вселенной на этом же поприще, увидя в описанном для себя какие-то паттерны или антипаттерны.
Терминал — это по сути самый обычный компьютер в нестандартном корпусе и с расширенным набором внешних устройств. Этот самый расширенный набор и определяет для какой услуги терминал предназначен.
Каков мог бы быть софт для такой железяки?
Предлагаю такой набор составляющих:
- Некоторая среда, в которой будут работать все компоненты терминального ПО (компонентная модель; framework; система плагинов).
- Компоненты-обертки вокруг драйверов устройств.
- Компонент обновления.
- Компонент настройки.
- Компоненты бизнес-логики.
- Компоненты пользовательского интерфейса.
- Вспомогательные / сервисные компоненты.
Если немного укрупнить части системы, то можно выделить всего две части: компонентная модель и компоненты системы, исполняемые в этой самой модели.
Компонентная модель
Не знаю для кого как, но для меня это самая интересная часть. Я долго себя обманывал, что мне больше всего интересно писать бизнес-логику, но оказалось это не так. Интереснее всего для меня — это «клей», т.е. как раз всяческие связующие вещи для систем, они же компонентные модели и вспомогательные библиотеки общего использования, т.е. как раз то, к чему программист ближе всего. А чтобы любить бизнес-логику надо, как минимум, обладать хоть какой-то экспертизой в этом самом бизнесе.
Как вы и на каких принципах построите компонентную модель это ваше дело. Можно построить взаимодействие компонент на основе асинхронной посылки событий друг другу через нечто. Можно все сделать на интерфейсах (в C++ терминах — это структурах с чисто виртуальными методами) и какую-нибудь модель их добывания, типа (сейчас скажу страшное слово, от которого многие плюются) COM. Или еще что-то. У всего будут и плюсы и минусы. Компоненты могут жить в рамках одного процесса или в рамках нескольких; так же есть свои нюансы. А можно забить на все эти архитектурные изыски, на декомпозицию и т.д., все свалить в один модуль или (учу плохому) прибегнуть к пластилиновой архитектуре и быстрее выползать на рынок.
Теперь немного поподробнее о каждом из подходов с моей точки зрения (буду рад вашу видеть в комментариях).
Асинхронно посылаемые события против интерфейсов
Для меня, с практической точки зрения написания бизнес-логики как раз терминалов самообслуживания, модель на интерфейсах куда удобнее модели, в которой все обмениваются между собой методом посылки событий. Почему? Рассмотрим такой казалось бы простой момент: дошли мы в какой-то покупке до внесения денег. Для этого, предположим, надо послать «купюрнику» сигнал о начале приема денег, дать некоторый промежуток времени на прием денег (что клиент, просто не пропадет, после первых 10 рублей из 10000 и, плюнув на все, не уйдет), после которого либо провести транзакцию, либо откат логики в какое-то начальное состояние и т.д. Тут, включая параноидальное
Что есть компоненты и где они должны жить?
Компонент — это некоторая единица, предоставляющая некоторый интерфейс. Это может быть обертка драйвера устройства, бизнес-логика и т.д. Физически компонент может обитать в динамической библиотеке (как и несколько компонент в рамках одной динамической библиотеки) и в рамках процесса, так и в разных исполняемых модулях и процессах, или иметь гетерогенную модель: компоненты в динамических библиотеках, объединяемые разными исполняемыми модулями и исполняемые в отдельных процессах-компонентных моделях (мда, «И тут Остапа понесло» (с) ).
Преимущества раскладывания компонент по динамическим библиотекам в том, что можно сделать некоторый конструктор и формировать наборы компонентов. Можно делать пакет для платежного терминала с определенным набором платежей, для терминала электронной очереди (например банка), сделать «музыкальный автомат» (в кафе сунул купюрку, послушал любимую песню. Тут конечно может быть диверсия: кто-то 2 августа закажет «Голубую луну» и быстро скроется из питейного заведения, а автомат весьма незамысловатыми действиями коллективного труда будет разобран на кварки, но это уже вопрос бизнес-логики управления контентом). Кроме того, при наличии компонент, систему на терминалах можно обновлять частями; как один из положительных моментов этого решения — снижение нагрузки на серверную часть, отвечающую за раздачу обновлений (при большой сети автоматов это уже может быть немаловажно).
Все хорошо складывается с раскладыванием компонент по динамическим библиотекам, и, казалось бы, что раскладка компонент по исполняемым модулям и выполнение в разных процессах не нужно. Вынесение компонент в отдельные исполняемые модули и, как следствие, выполнение в других процессах — это как минимум возня с IPC. Иногда пригождается. Вы не сталкивались с проблемой, когда несколько библиотек конфликтуют в одном модуле при сборке или куда хуже при исполнении? К сожалению, такое бывает. Тут разнесение на разные исполняемые модули и помогает. Второй момент немного параноидальный: вы не хотите, чтобы какой-то сторонний закрытый код, поставляемый в виде библиотеки (например, драйвер одного из устройств) имел доступ к адресному пространству вашего процесса, так как он, например, по непонятным причинам иногда «роняет процесс», вот еще кандидат на вынесение в отдельный процесс. Это множество ситуаций можно расширять, оно не будет никогда пустым, но его элементы — это исключительные редкие ситуации.
Так некоторым количеством букв я реабилитировал необходимость существования некоторой среды-компонентной модели. Если вы не планируете большую гибкую систему, то вполне на первое время все можно делать в рамках одного модуля и процесса. Только не скатывайтесь к пластилиновой архитектуре.
Компоненты-обертки вокруг драйверов устройств
Тут особо не пофилософствуешь. Задача простая, решения могут быть сложными… Задача: адаптировать интерфейс работы с устройствами к некоторым внутренним интерфейсам разрабатываемого ПО, которым, например, могут пользоваться модули бизнес-логики. Сложность решения заключается в изучении документации на соответствующее устройство или в поиске таковой перед тем как что-то изучать. Далее все просто — или писать обертку над API предоставленного драйвера, или самому формировать загадочные последовательности битов, описанные в спецификации к устройству и слать их в соответствующий порт, к которому подключено устройство.
Компонент обновления (Updater)
Система обновления ПО терминала может быть не так проста как хотелось бы. Если нет никакой компонентной архитектуры, а обновлять надо всего один исполняемый модуль и некоторый медиаконтент, то проблем меньше. А если еще есть некоторый временной интервал в сутках, когда система может полностью «заняться собой» и никого не обслуживать, то все совсем просто.
Если у вас компонентная модель с кучей разного рода компонент, то тут шире пространство для размышления о построении модуля обновлений. Несомненный плюс — это то, что не надо обновлять все, а только те, которые изменились. За такую гибкость, как и за все в этой жизни, придется заплатить. В данном случае большим контролем за тем, чтобы обновляемые модули могли работать с версиями тех, которые не были обновлены.
Обновление без остановки системы. Скачивается новая версия модуля, старый отгружается из памяти, новый загружается и система продолжает работать. Светлое будущее, коммунизм, и Ленин такой молодой, и октябрь у нас впереди. Утопия в реальности… Так как большая сложность разработки компонент для такого механизма обновления. По моему мнению, с которым вы, конечно, можете и не согласиться, при обновлении система должна завершить правильно выполняемую операцию, полностью остановить работу, произвести обновление модулей или их части и начать работу с некоторой базовой точки, например, с экрана приветствия. Такое упрощение сильно упростит разработку компонент. Можно сделать небольшое исключение: не останавливать систему при обновлении некритического контента.
В идеале на систему обновления можно возложить и начальную установку ПО. Т.е. дилер приобретает терминал, на котором кроме среды исполнения, системы обновления и компонента настройки нет ничего, задает некоторые базовые параметры (например: адрес сервера и некоторый идентификационный номер), система обновления скачивает все, что положено данному дилеру согласно его набору услуг, на который был заключен договор с поставщиком, а идентификационный номер как раз и определит необходимый пакет ПО.
Компонент настройки
Некоторый компонент, который даже на «пустом» терминале будет иметь пользовательский интерфейс (может даже написанный на родном API ОС или с использованием таких библиотек как Qt) для задания как минимум начальных параметров, о которых было написано выше. Компоненты же, в свою очередь, могут иметь некоторый программный интерфейс, с помощью которого компонент настройки может получать от них списки настраиваемых параметров, допустимые, текущие значения и значения по умолчанию для визуализации и изменения. Так же, помимо пользовательского интерфейса, данный модуль может работать с конфигурационными файлами. Можно декомпозировать и декомпозировать систему и вынести хранение всех заданных параметров в отдельную базу и предоставить соответствующий программный интерфейс, но, немного покривя душой, эту функциональность можно совместить и с компонентом настройки.
Компонент бизнес логики
В этих компонентах размещаются все сценарии оказываемых услуг терминалами самообслуживания. Таких компонент может быть множество, так как один и тот же терминал может производить и оплату сотовой связи и билеты в цирк теоретически продавать. Все пихать в один компонент логики не стоит.
Предположим изначально работает только логика экрана приветствия, в функции которой входит дать пользователю выбор необходимой ему услуги. После выбора услуги передается управление логике оказания данной услуги, которая уже как-то взаимодействует с нужными ей устройствами посредством компонент-оберток драйверов (принтер, купюроприемник и прочими), «переключает» экраны согласно заданному сценарию (страницы веб-браузера например), реагирует на активность пользователя. Отсюда можно сделать некоторое заключение, что при обновлении можно поставлять пакеты из модулей бизнес-логики и связанного с ней контента (например, веб-страницы для пользовательского интерфейса).
Дилеру же работа с новой услугой может быть добавлена приходом такого пакета в обновлении, без замены чего-то уже установленного (вот они плюсы компонентности при расширении спектра предоставляемых услуг).
Компоненты пользовательского интерфейса
Эта часть системы для отображения контента и реагирования на действия пользователя.
Терминалы могут быть как с одним экранои, так и с несколькими. Как правило интерактивен только один экран, остальные могут служить для показа рекламы.
Что же использовать для создания пользовательского интерфейса?
Наиболее распространен веб-интерфейс (набор html + картинки), чуть менее flash. Остальные варианты создания пользовательского интерфейса уже куда менее рентабельны.
Популярность html при разработке пользовательского интерфейса в том, что он дает большую скорость разработки, а движки для отображения html относительно легко встраиваются в собственное ПО. Реагировать на действия пользователя можно через какой-нибудь внутренний интерфейс движка, а если таковой не обнаружен или не дает той гибкости, которая нужна разработчику, то можно в своем модуле пользовательского интерфейса построить мини веб-сервер, например, используя libevent, и общение html интерфейса с остальной частью организовать посредством http запросов с уже вашей специфичной обработкой.
Разработка интерфейса на flash так же интересна и дает возможности для создания множества красивых эффектов, но немного хуже встраивается в собственное ПО. Несколько лет назад мне так и не удалось сделать нормальную связку flash ExternalInterface и C++ под linux (под windows все было гораздо лучше с этим вопросом). Как выход из ситуации можно предложить описанное выше решение со встроенным мини веб-сервером.
Html и flash это то, что видит пользователь, но бизнес-логика не должна работать с движками html и flash. За эту часть должен отвечать компонент пользовательского интерфейса, задача которого инкапсулировать всю работу с движками и предоставить бизнес-логике некоторый внутренний программный интерфейс. Тут есть одна тонкость: отучить разработчиков компонента пользовательского интерфейса и разработчиков html и flash втаскивать хоть какие-то мало мальские куски логики в свою часть. Парадокс, но хоть ладаном окуривай и кадилом среди разработчиков тряси, все равно будут тащить куски логики к себе. Феномен мне пока не понятен. Все осознают и соглашаются, что UI ничего не должен делать, кроме как посылать события от элементов управления в бизнес-логику и отображать тот контент, на который указала логика, но нет; чуть зазевался и уже прикрутили на нажатие какой-то кнопки переход на некоторый экран, а потом часы отладки нескольких частей команды разработчиков и препирания на тему, что это не в его части ошибка. Желание вбить лом в многослойную архитектуру, пробив все ее слои, для решения маленькой частной проблемы неискоренимо в любой части, не только в UI. Все должна делать логика, а UI только отображать, на что указали и посылать в логику события от элементов управления.
Заключение
Все что описано выше не беспочвенный полет мыслей, а вполне реально разрабатываемая когда-то ранее система. У нас было, можно сказать, все: терминалы, ПО для терминалов, серверная часть. Оставалось не так много с технической стороны, но не взлетело…
В умных книжках о стартапах бывают описаны причины, по которым стартап не взлетает. Есть одна описанная очень интересная причина: опережение идеей времени. Т.е. рынок еще не готов к принятию такого продукта, который выкатывает стартапер и продукт становится невостребованным.
Иногда, смотря на некоторые проекты, видишь какие-то куски того, что пытались сделать несколько лет назад. По этой причине хорошо себя тешить и отвечать другим, что проблема была именно в невостребованности из-за опережения. Но на самом деле это не так и стартапы могут погубить куда более простые причины, например, нечеткость курса при движении к цели, частые перемены в требованиях (хотелках).
«Часто пересаживаемые яблони редко плодоносят». (с) «Менеджер мафии. Руководство для корпоративного Макиавелли».
Всем спасибо за внимание!
Автор: NYM