Вступление
В эпоху цифровизации, Вы когда-нибудь задумывались, существуют ли способы значительно снизить затраты на разработку автоматизации какого-либо процесса? И речь не про жалкую экономию в 5-10%, а именно экономию в разы, когда от идеи до ввода в промышленную эксплуатацию - достаточно пару недель разработки. Не месяцы и не годы подготовки тендеров, ФТТ, документов, а только собственные силы. Когда работающий прототип можно "накидать" за пару недель и сразу демонстрировать работающее решение, параллельно доводя его до совершенства. Особенно это актуально для инжиниринговых, конструкторских или производственных предприятий, которые по своей сути не являются разработчиками ИТ, но вынуждены здесь и сейчас, малыми затратами, обеспечивать собственные нужды различными ИТ-решениями.
И если вы подумали, что речь пойдёт про "разбушевавшийся" тренд с LLM и vibe coding - НЕТ.
Сегодня я немного приоткрою занавес тайн и постараюсь рассказать про те самые области тьмы, которые мало известны широкой публике и до этого момента были доступны только крупнейшим предприятиям и холдингам, которые в течение уже 15 лет успешно цифровизируют свои внутренние процессы. Также детально покажу, как с помощью только одной лишь конфигурации можно сделать полноценный PoC и MVP.
Рецепт
В чем же заключается этот рецепт, который знают только "избранные"? А всё до жути просто - для цифровизации внутренних процессов, крупные компании выбирают мощные платформы общего назначения, и вся суть сводится не к полноценной разработке "с нуля" (с высокими затратами и высоким порогом входа в разработку), а к кастомизации уже существующей архитектуры под конкретные процессы (т.е. адаптация системы под уникальные нужды, с помощью различных конфигураций, ну и кода естественно).
Можно сказать, что такие платформы можно отнести к low-code классу. Частично это так, но не совсем. Low-code во многом применяется только в части построения модели данных или UI, но для сложных процессов в любом случае требуется полноценное программирование. Платформа как раз "убирает" рутинные операции, а разработчик сосредотачивает всё своё время на программировании конкретного процесса, используя простые платформенные абстракции.
Немного конкретики
Вроде бы на ум в первую очередь приходят ERP-гиганты вроде SAP и 1С, которые не нуждаются в представлении и также обладают платформенными свойствами. Но сегодня они не являются объектом рассмотрения. Да, их используют для построения решений, выходящих за рамки их целевого назначения, но не так часто и так охотно, в отличие от других представителей сегодняшней темы. Существуют более "приземлённые" варианты, с более гибкими инструментами и возможностями.
Приведу популярные примеры, "широко известные в узких кругах":
Hexagon SmartPlant Foundation (ранее Intergraph SmartPlant Foundation) - зарубежная кастомизируемая платформа для решения внутренних задач инжиниринга.
Основной язык программирования: С#.TDMS - отечественная кастомизируемая платформа, с готовыми модулями по документообороту.
Основной язык программирования: Microsoft Visual Basic и С#.Dassault Systèmes 3DExperience (ранее Dassault Systèmes Enovia, ещё ранее matrixone) - зарубежная мощнейшая кастомизируемая PLM, платформа, широкого спектра применения.
Основной язык программирования: Java, JSP, Angular.
Подобных платформ относительно много. Я привёл только те, с которыми хорошо знаком.
Касаемо 3DExperience - это вообще лидер в области кастомизации. Она и является моей специализацией уже много лет. И проще перечислить что мы на ней НЕ делали, чем все её возможности.
И да, изначально (лет 20 назад) PLM - это только про жизненный цикл изделия. Но поскольку возможности такой платформы предполагают гибкую архитектуру модели данных, разработчики быстро догадались применять их для решения любых повседневных задач, а не только хранить там сборочные модели с разбивкой вплоть до болтиков. Документооборот - пожалуйста, управление требованиями - легко, управление проектами - уже завтра будет готово и т.д. Поэтому сегодня, я бы называл такие решения - суперплатформами. Что думаете об этом?
Положительные стороны при использовании платформ
У меня нет задачи "уговорить" вас использовать платформы, а только познакомить с возможностями, поэтому не вижу смысла переписывать все рекламные материалы с десятками точных выверенных формулировок о причинах выбрать конкретный софт. Тем не менее, контекст статьи намекает, что несколько плюсов всё же нужно перечислить, поэтому на собственном опыте, кратко опишу своими словами основные:
Любая платформа идеально вписывается в инфраструктуру и архитектуру любого предприятия.
Снимает вопросы кем, на чём и как разрабатывать новый модуль или функциональность.
В будущем предотвращает расширение "зоопарка" различных систем предприятия, стандартизируя единый стек разработки.
Освобождает от необходимости интеграций между различными подсистемами (при условии, что все необходимые подсистемы реализованы в одной платформе).
Ну и самое главное - платформа даёт возможность здесь и сейчас вносить изменения своими силами!
Ох, сколько раз я слышал, как страдают коллеги в соседних подразделениях из‑за того что им купили софт, в котором нельзя кастомизировать кнопку, не заключив соответствующий договор с подрядчиком, и предварительно не заложив бюджет ещё с прошлого года.
А минусы?
Безусловно и минусы тоже есть. Самое главное - это зависимость от конкретного вендора и его действий. В связи с последними событиями, иностранные вендоры прекратили поддержку продуктов в РФ, и сейчас покупка (или продление) лицензий стала невозможной. Конкретно 3DExperience (от французской компании Dassault Systèmes), из-за отсутствия возможности докупить и продлить лицензии, сейчас отважно погибает на глазах у предприятий, отметив "20 лет" успешного внедрения.
Можно ещё поднять вопрос ценовой политики - на некоторые платформы цена может быть весьма высока, но у меня нет точных данных. С другой стороны, тут ситуация похожа на приобретение лицензионной промышленной СУБД (например, Oracle). Предприятия платят за них, потому что получают готовую, отказоустойчивую и масштабируемую основу для своих критически важных данных и сервисов. Платформа управления данными - это такая же стратегическая инфраструктурная инвестиция, но на уровень выше, не только для хранения, но и для быстрого создания приложений поверх нее.
10 признаков "настоящей" платформы управления данными
Я повидал достаточно платформ и пошарился у них "под капотом", чтобы сформулировать мнение о признаках хорошей платформы. Каждая должна обладать как минимум следующими возможностями, прямо "в коробке":
Встроенные сервисы аутентификации и авторизации пользователей.
Статусная и ролевая модель доступа к контенту (данным).
Гибкая кастомизация схемы модели данных: атрибуты, типы, связи, жизненный цикл, статусы, триггеры и т.д.
Связи между объектами - отдельные сущности.
Наличие версионности объекта.
Управление файлами.
Доступ к контенту (данным) через высокоуровневый API с автоматической проверкой доступа, без необходимости написания нативного SQL.
Универсальный REST API для CRUD-операций с любыми сущностями платформы.
Если для взаимодействия с контентом требуется создание соответствующего специального REST сервиса, для конкретной сущности - то это не может называться платформой (тут ближе название - фреймворк). У "настоящей" платформы уже есть встроенный универсальный сервис для взаимодействия с любыми записями (имя интересующей сущности передаётся в теле запроса).Гибкий кастомизируемый UI конструктор, с широкой компонентной базой.
Полноценный аудит: история изменений контента (данных) с указанием кто, что и когда изменил.
Техническая часть
А теперь перейдём к технической части и посмотрим, как платформа помогает решать повседневные задачи по цифровизации процессов.
Учитывая мою специализацию - 3DExperience, я мог бы легко показать, как осуществляется кастомизация, на её примере. Проблема в том, что её особенности могут отпугнуть, поскольку в качестве основного инструмента кастомизации является внутренний, никому неизвестный язык (mql). Не подготовленный читатель не поймёт о чём речь.
Но у меня есть вариант получше - я покажу пример кастомизации, используя абстрактный JSON-подобный подход. В качестве наглядного референса возьму философию и концепции, реализованные в JMatrixPlatform - развивающееся отечественное решение.
Возможно, в будущем расскажу более подробно про JMatrixPlatform, про историю создания, про дизайн ядра, про технические решения и прочие вкусности. Есть чем поделиться и информации хватит на цикл статей.
Кастомизация BE
Сразу скажу, что достаточно сложно выбрать достойный прототип для демонстрации возможностей платформы, чтобы показать удобство взаимодействия разработчика с инструментами конфигурации и при этом не уйти в 8-ми часовую лекцию.
Чтобы пример был достаточно понятен и красочен для широкой публики - я долго перебирал в голове множество интересных вариантов. И остановился на следующем: реализация цифровой модели карьерного самосвала. Но не всех его агрегатов, а только его базовых свойств, плюс колёса/шины. Здесь реализуем модель данных и UI. На выходе будет полноценно работающий модуль. И на всё уйдёт не больше 3 часов конфигурации, включая UI!
Модель данных
Можно сразу приступать к подготовке и реализация модели (схемы) данных. Здесь понадобится сам самосвал, с атрибутами VIN и Гос.номер, справочник с марками шин, с атрибутом Диапазон допустимых давлений и непосредственно колёса, со связью до марки. Вот так это выглядит на деле:

Приступим к кастомизации. Формируем модель данных:
Атрибут VIN:
{ "name": "VIN", "data_type": "string", }
Атрибут Гос.номер:
{ "name": "RegNumber", "data_type": "string" }
Атрибут Диапазон допустимых давлений, атм:
{ "name": "PressureRange", "data_type": "real_range", "dimension": null }
Атрибут Расположение колеса со списком значений FL (front left), FR (front right) и т.д.:
{ "name": "WheelLocation", "data_type": "string", "choice": ["FL", "FR", "RL", "RR"] }
Основные типы данных атрибутов: string, string multi, real, real_multi, real_range, integer_integer_milti, timestamp, timestamp_range, boolean. Для real* атрибутов также доступна возможность указать группу ЕИ (единиц измерений), с автоматическим пересчётом значений из одной ЕИ в базовую (указывая значение 1 МПа, платформа автоматически пересчитывает и сохраняет базовое значение равное 1 000 000 Па).
Тип Самосвал с атрибутами VIN и Гос.номер:
{ "name": "DumpTruck", "attributes": [ "VIN", "RegNumber" ] }
Тип Колесо:
{ "name": "Wheel", "attributes": [] }
Тип Шина с атрибутом Диапазон допустимых давлений:
{ "name": "Tire", "attributes": [ "PressureRange" ] }
Жизненный цикл Самосвала cо статусами Создан, Эксплуатация, Консервация, Выведен из эксплуатации и отдельными доступами на каждом:
{ "name": "DumpTruck", "types": ["DumpTruck"], "states": [ { "name": "New", "role_access": [ { "name": "PUBLIC", "rights": ["read", "create", "delete", "modify", "file_upload", "file_download"] } ] },{ "name": "Operation", "role_access": [ { "name": "PUBLIC", "rights": ["read", "modify", "file_upload", "file_download"] } ] },{ "name": "Conservation", "role_access": [ { "name": "PUBLIC", "rights": ["read", "modify", "file_upload", "file_download"] } ] },{ "name": "Decommissioned", "role_access": [ { "name": "PUBLIC", "rights": ["read"] } ] } ] }
Жизненный цикл Simple для колёс и справочника шин:
{ "name": "Simple", "types": ["Wheel", "Tire"], "states": [ { "name": "New", "role_access": [ { "name": "PUBLIC", "rights": ["read", "create", "delete", "modify"] } ] } ] }
Связь СамосвалКолесо с атрибутом Расположение:
{ "name": "DumpWheel", "attributes": ["WheelLocation"], "from_types": ["DumpTruck"], "from_cardinality": "many", "to_types": ["Wheel"], "to_cardinality": "one" }
Связь КолесоШина:
{ "name": "WheelTire", "attributes": [], "from_types": ["Wheel"], "from_cardinality": "many", "to_types": ["Tire"], "to_cardinality": "one" }
Вот здесь нужно пояснить, что любые связи между объектами в платформе - это тоже отдельные сущности со своим UUID, которые также могут содержать конкретные значения атрибутов. Ещё связи могут быть не только между объектами, но и между объектом и другой связью или связями. Но об этом в другой раз.
Ну сформировали json'чики, и что дальше?
А дальше - по сути ничего больше не нужно. Всё готово. Запускаем платформу и сразу можно приступать к созданию конкретных записей объектов и связей между ними.
А как же JPA, SQL, спросите вы? Повторюсь - ничего не нужно, не волнуйтесь, платформа прекрасно понимает что и как нужно сделать, без дальнейшего участия разработчика. Главное правильно сформировать схему.
Обращаю ваше внимание, что такие базовые свойства как: type, policy, state, code, title, description, full_code, full_title, originated, modified - уже присутствуют в каждом объекте и создавать для них отдельные атрибуты не требуется!
Создаём самосвал и 4 колеса к нему
Создаём самосвал, с помощью запроса POST /objects/ - получаем UUIDDumpTruck
{ "type": "DumpTruck", "policy": "DumpTruck", "code": "БелАЗ 7545", "title": "Карьерный самосвал грузоподъёмностью 45 тонн", "attributeValues": { "VIN": { "value": "0123456789" }, "RegNumber": { "value": "x777xx77" } } }
Создаём колесо FL, с помощью запроса POST /objects/ - получаем UUIDFL
{ "type": "Wheel", "policy": "Simple", "code": "FL", "title": "Левое переднее колесо", "attributeValues": {} }
Также, через POST /objects/ создаём ещё 3 колеса и получаем UUIDFR, UUIDRL, UUIDRR.
Создаём шину, с помощью запроса POST /objects/ - получаем UUIDTire.
{ "type": "Tire", "policy": "Simple", "code": "18.00-25 ВФ-76БМ нс.32 Е3 ТТ", "title": "Шина для строительных, дорожных, подъемно-транспортных машин и вездеходов.", "attributeValues": { "PressureRange": { "inputValueMin": 6, "inputValueMax": 6.5 } } }
Указываем для каждого колеса соответствующую марку шины, с помощью запроса POST /connections/ - получаем соответствующий UUID:
{ "relationship": "WheelTire", "from_id": "UUIDFL", "to_id": "UUIDTire", "attributeValues": {} }
Также, через POST /connections/ создаём ещё 3 связи.
И прикрепляем созданные колёса к самосвалу, в каждом указываем соответствующий атрибут WheelLocation с помощью запроса POST /connections/
{ "relationship": "DumpWheel", "from_id": "UUIDDumpTruck", "to_id": "UUIDFL", "attributeValues": { "WheelLocation": { "value": "FL" } } }
Также, через POST /connections/ создаём ещё 3 связи между самосвалом и остальными колёсами.
Итоговый объект самосвала будет выглядеть так, используем запрос GET /objects/UUIDDumpTruck:
{ "id": "UUIDDumpTruck", "type": "DumpTruck", "policy": "DumpTruck", "state": "New", "code": "БелАЗ 7545", "release": "", "title": "Карьерный самосвал грузоподъёмностью 45 тонн", "originated": "2015-01-20T09:45:27.250293Z", "modified": "2015-01-20T09:45:27.250293Z", "fullCode": "", "fullTitle": "", "description": "", "files": [], "attributeValues": { "VIN": { "value": "0123456789" }, "RegNumber": { "value": "x777xx77" } }, "fromConnections": [], "toConnections": [ { "id": "UUIDFL", "relationship": "DumpWheel", "from_id": "UUIDDumpTruck", "to_id": "UUIDFL", "attributeValues": { "WheelLocation": { "value":"FL" } } },{ "id": "UUIDFR", "relationship": "DumpWheel", "from_id": "UUIDDumpTruck", "to_id": "UUIDFR", "attributeValues": { "WheelLocation": { "value":"FR" } } },{ "id": "UUIDRL", "relationship": "DumpWheel", "from_id": "UUIDDumpTruck", "to_id": "UUIDRL", "attributeValues": { "WheelLocation": { "value":"RL" } } },{ "id": "UUIDRR", "relationship": "DumpWheel", "from_id": "UUIDDumpTruck", "to_id": "UUIDRR", "attributeValues": { "WheelLocation": { "value":"RR" } } } ] }
Минутка юмора.В платформе 3DExperience для просмотра всей информации о конкретном объекте присутствует специальная команда: print businessobject ID и её самый часто используемый краткий вариант - print bus ID. В простонародье мы называли её "распечатать автобус" :)
В итоговом JSON видны все базовые свойства и кастомные атрибуты объекта, а также его связи с другими объектами, в данном случае - 4 связи с колёсами. Есть автоматические свойства, такие как originated, modified, заполняемые без вмешательства разработчиков. Присутствует статус, который меняется через специальные запросы POST /objects/UUID/promote и POST /objects/UUID/demote. Можно посмотреть историю изменений GET /objects/UUID/history, создать версию объекта POST /objects/UUID/release, копию POST /objects/UUID/copy, и много чего ещё.
А если мы забыли указать для самосвала грузоподъёмность? Что теперь делать? Ничего страшного, добавляем новый атрибут и модифицируем тип:
Атрибут Грузоподъёмность:
{ "name": "Capacity", "data_type": "real_range", "dimension": null }
Тип Самосвал:
{ "name": "DumpTruck", "attributes": [ "VIN", "RegNumber", "Capacity" ] }
Теперь вместе с другими кастомными атрибутами VIN, Гос.номер можно указывать и Грузоподъёмность, как для новых, так и для уже созданных самосвалов:
POST /objects/UUIDDumpTruck
{ "type": "DumpTruck", "policy": "DumpTruck", "code": "БелАЗ 7545", "title": "Карьерный самосвал грузоподъёмностью 45 тонн", "attributeValues": { "VIN": { "value": "0123456789" }, "RegNumber": { "value": "x777xx77" }, "Capacity": { "inputValue": 45 } } }
Конфигурация UI
Если устали - листайте к сразу заключению.
Кратко, на примере справочника шин, покажу кастомизацию UI, главной особенностью которой - все изменения осуществляются на стороне BE. Да, да, вы не ослышались! Чтобы поменять FE, нужно опять кастомизировать BE. Иначе говоря, BE хранит в себе все базовые настройки форм, таблиц, панели инструментов и т.д., а FE только считывает конфигурацию и рендерит UI в соответствии с настройками.
Справочник шин
Чтобы отображать справочник в UI, понадобится подготовить 4 связанных компонента:
Таблица.
Запрос, извлекающий шины.
Панель инструментов таблицы.
Команда (кнопка), которая открывает справочник.
Таблица TiresBrand, тут всё очень просто:
{ "code": { "label": "Код", "columnWidth": 200, "settings": {} }, "title": { "label": "Наименование", "columnWidth": 200, "settings": {} }, "PressureRange": { "label": "Диапазон допустимых давлений, атм", "columnWidth": 200, "settings": {} } }
Запрос TiresBrandAll, который извлекает все шины из платформы. Можно добавить условия фильтрации по статусам или по атрибутам, но сейчас это не нужно, просто ищем все шины:
{ "type": "Tire",//имя типа из модели данных "code": "*", "release": "*", "where" : null, "select": [ "code", "title", "PressureRange"//имя атрибута из модели данных ], "pageSize": 1000, "limit": 5000, }
Панель инструментов TiresToolbar таблицы, с кнопками Создать и Удалить:
{ "name": "TiresToolbar", "childs": [ "TireCreate",//команда создания шины, с указанием формы создания, типом и ЖЦ создаваемого объекта "CommonRowsRemove"//команда удаления строчки таблицы ] }
Здесь можно посмотреть конфигурацию для кнопок TireCreate и CommonRowsRemove
Команда (кнопка) создания шины TireCreate:
{ "name": "TireCreate", "label": "Создать", "href": { "url": "../form/TireCreate",//TireCreate - имя формы создания шины "requestParams": { "header": "Создать", "actionbar": "CommonActions",//панель действий, с кнопкой создать "type": "Tire",//имя создаваемого типа из модели данных "policy": "Simple",//имя создаваемого ЖЦ из модели данных "target": "RIGHT"//открыть форму справа } } }
Форма создания шины TireCreate:
{ "JBTitle": { "label": "Наименование", "description": null, "placeholder": null, "value": { "original": [], "input": [] }, "required": true, "editable": true, "hidden": false, "showClear": false, "hideLabel": false, "autoFilter": false, "clientChanged": false, "renderComponent": "JTextbox", "choice": null, "href": null, "selectOptions": null, "units": null, "validation": null, "onChange": null, "settings": {} }, "ATRTireAcceptablePressure": { "label": "Диапазон допустимых давлений, атм", "description": null, "placeholder": null, "value": { "original": [], "input": [] }, "required": true, "editable": true, "hidden": false, "showClear": false, "hideLabel": false, "autoFilter": false, "clientChanged": false, "renderComponent": "JRealRange", "searchTerm": null, "choice": null, "href": null, "selectOptions": null, "units": null, "onChange": null, "settings": { "step": "0.1", "min": "0", "max": "15" } } }
Команда (кнопка) удаления CommonRowsRemove - стандартная переиспользуемая команда, подходит для удаления любых объектов:
{ "name": "CommonRowsRemove", "label": "Удалить", "settings": { "rowSelection": "MULTI", "confirmMessage": "Удалить выбранные строчки?" }, "href": { "composable": "useTable", "js": "jmxRowsRemove", "settings": {} } }
Команда (кнопка) RefTires, которая открывает таблицу TiresBrand, с панелью инструментов TiresToolbar, с данными из запроса TiresBrandAll:
{ "name": "RefTires", "label": "Каталог шин", "href": { "url": "../table/TiresBrand",//TiresBrand - имя таблицы "requestParams": { "toolbar": "TiresToolbar",//имя панели инструментов "rowSelection": "MULTI",//можно выбрать несколько строк "inquiry": "TiresBrandAll",//имя запроса "target": "CONTENT"//таблица открывается в текущем окне } } }
Теперь необходимо разместить команду RefTires в соответствующей панели главной страницы, а именно в MyDesk, в которой уже размещены различные модули:
{ "name": "MyDesk", "childs": [ "ProjectManagementDesk", "TestDesk", "DocumentsFlowDesk", "RefTires"//имя новой команды с каталогом шин ] }
Результат
После внесения изменений, платформа отобразит соответствующую конфигурацию в UI. На панели MyDesk слева появилась соответствующая категория - Каталог шин, которая отображает таблицу с шинами и кастомную панель инструментов с кнопками Создать и Удалить. Если нажать Создать, то платформа отобразит справа форму создания шины, как и указывалось в соответствующей кнопке:

А сконфигурировав соответствующий навигатор самосвала, с нужными категориями, можно добиться следующего результата, ни написав ни строчки js кода:

Заключение
Надеюсь у меня получилось рассказать, на конкретном примере, что кроме популярных "локальных" фреймворков существуют ещё полноценные мощные платформы для управления данными. Как минимум теперь вы знаете об их существовании. Как максимум - кого-то могут заинтересовать их возможности для внедрения у себя на предприятии.
Пример с самосвалом ярко отражает суть - за ~3 часа конфигурации создается работающий модуль с моделью данных, бизнес-логикой и UI, что в классической разработке заняло бы недели, а то и больше. При этом, платформе абсолютно без разницы какой объект планируется цифровизировать: оборудование, изделие, документация или какие-то финансовые потоки и показатели. Гибкая модель позволяет быстро конфигурировать сущности, включая разветвлённые связи между ними.
Я искренне верю, что у платформ управления данными определённо есть будущее, они прекрасно себя чувствуют в некоторых областях ИТ. На них есть спрос, есть конкуренция между отечественными решениями. Самое интересное, что при поиске платформы не так-то просто сделать выбор, детально не попробовав каждую из них "в деле". Но в случае "удачного" выбора - предприятие может сэкономить на ИТ достаточное количество ресурсов и направить их в основную сферу деятельности, приносящую доход.
