Вступление
В эпоху цифровизации, Вы когда-нибудь задумывались, существуют ли способы значительно снизить затраты на разработку автоматизации какого-либо процесса? И речь не про жалкую экономию в 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, что в классической разработке заняло бы недели, а то и больше. При этом, платформе абсолютно без разницы какой объект планируется цифровизировать: оборудование, изделие, документация или какие-то финансовые потоки и показатели. Гибкая модель позволяет быстро конфигурировать сущности, включая разветвлённые связи между ними.
Я искренне верю, что у платформ управления данными определённо есть будущее, они прекрасно себя чувствуют в некоторых областях ИТ. На них есть спрос, есть конкуренция между отечественными решениями. Самое интересное, что при поиске платформы не так-то просто сделать выбор, детально не попробовав каждую из них "в деле". Но в случае "удачного" выбора - предприятие может сэкономить на ИТ достаточное количество ресурсов и направить их в основную сферу деятельности, приносящую доход.
