В прошлой статье я познакомил вас с существованием малоизвестных "суперплатформ" управления данными. На абстрактном примере реализации показал как легко кастомизируется платформа - от идеи, к готовому UI. У кого-то это вызвало отвращение, у кого-то просто шок, а кто-то увидел возможность.

Настало время на деле показать как с помощью современного стека разработки (который возможно уже через пару лет станет опять старым) - дать второе дыхание морально устаревшим системам из прошлого, взяв из них лучшее, а именно философию и надёжные концепции и при этом не страдать от старого, дурно пахнущего legacy.

В общем расскажу про импортозамещение Dassault Systèmes 3DExperience, далее Enovia, а точнее - той её части, которая на протяжении многих лет востребована многими крупными предприятиями: гибкого движка для построения бизнес-приложений, с возможностями управления жизненным циклом, без привязки к тяжелому САПР.

Предпосылки

Всем известные события принесли с собой уход иностранных вендоров с рынка, в том числе и Dassault Systèmes, а вместе с ним испарилась и возможность покупать лицензии на использование ПО. Некоторым всё же удалось в последний раз купить "бесконечные" лицензии, но их количество весьма ограничено и о дальнейшем внедрении Enovia, как платформы для автоматизации, можно забыть навсегда.

Конечно же была предпринята попытка найти готовое отечественное решение. Были устроены "смотрины" порядка 10 вендров, которые на пресейл стадии убеждали, что они супергибкие и классные, но покопавшись у них под капотом, оказалось что это не так. В боль��инстве случаев предлагалась некая стандартизация для бэка и БД, но напрочь отсутствовала конфигурация UI и всё предлагалось опять делать с нуля самим. В целом такой подход не сильно отличался от классического JPA/Hibernate. В итоге нашлась пара реальных кандидатов, которые действительно понравились. Но в 10 признаков "настоящей" платформы прошлой статьи не уложился никто. По результатам исследования, лично я остался наедине с мыслью - "не то пальто" и стойким ощущением, что можно сделать гораздо лучше. Я не участвовал в принятии окончательного решения, но от покупки готового решения отказались.

С этого момента, при необходимости разработки новой функциональности, предприятия вынуждены были перейти на открытый стек или привлекать внешних подрядчиков на "сложные" задачи. Спустя некоторое время обнаружилось, что такая разработка обходится дороже и дольше в несколько раз.

Невыдуманные истории

Новая "модная" реальность показала, что теперь вместо одного фуллстек и одного бизнес аналитика Enovia, минимум нужны: back, front, дизайнер макетов, solution-архитектор, системный аналитик, scrum мастер и т.д. На выходе, спустя несколько месяцев, за N-адцать миллионов рублей, получаем глючную "говносистему" с хардкодом и прибитыми гвоздями процессами. Чтобы внести банальные изменения, нужно обязательно также собрать все 6 человек, чтобы каждому, по цепочке, поставить задачу на исправление.

Если это было с привлечением подрядчика, то нужно заложить бюджет, желательно ещё в "прошлом году". И первое, что говорят потребители - это было долго и дорого, давайте теперь дорабатывать её собственными силами! Здорово, правда?! Учитывая что, внутренний стек - это, например, React, а подрядчик выбрал Angular и никто внутри не берёт систему на поддержку и доработку. В лучшем случае получаем очередную выкинутую в стол "поделку", а в худшем, под дулом пистолета её начинают "шаманить" местные.

А как же меня триггерит от необходимости интеграции с очередной "поделкой", когда оказывается, что там в API забыли предусмотреть некоторые базовые CRUD операции, а уж о поиске по различным условиям вообще мечтать не приходится. Мало того, некоторые вообще упорно игнорируют реализацию внешнего API без прямого указания этого в ТЗ, а те которые делают - вызывают дичайший рвотный рефлекс. А от разнообразия реализаций API, ключевых органов управления системой, становится больно.

Часто наблюдаю, что в БД отсутствуют базовые колонки author_id, created, modified, и чтобы забрать только новые или изменённые записи, с указанной даты, они предлагают мне вычитывать все 100500 записей и самому разбираться какие новые, а какие нет. А всё потому что при проектировании оказалось, что заказчик не думал об этом (хотя и не должен), а участвующий аналитик просто тупой и не способен думать на два шага вперёд. Разработчики без лишних вопросов сделали как просили, потому что думать об этом - сегодня тоже не их задача.

Смотришь на справочники и поражаешься, потому что не видишь там элементарных статусов, чтобы отсечь например исторические или устаревшие элементы. Зачем? Заказчик же изначально не просил! Но предусмотреть это заранее - видимо сегодня невероятно сложно.

Если обнаружилось, что не заложили в UI кнопки "Редактировать" или "Удалить", потому что думали что объекты нельзя менять по процессу, а затем срочно понадобилось - сразу попадаете на месяц ожидания и доработки. Потому что надо отстоять очередь за разработчиками, чтобы на бэке сделать дополнительные endpoint'ы, прописать логику, дописать слой взаимодействия с БД, затем привлечь фронта, чтобы он нарисовал новые кнопки и добавил взаимодействие с бэком.

И такое я встречаю каждый день. Одни и те же проблемы из проекта в проект. Это только вершина айсберга. Безусловно, я видел и адекватные системы, разработанные с нуля, где действительно продуманы все нюансы. Но таких всё меньше и меньше.

Разве можно по другому?

Конечно можно - при использовании платформ, даже не обращаешь внимание на такие детские болячки, всё просто уже заложено и предусмотрено самой философией. При разработке сразу видно, что тут явно не хватает простейшей кнопки, свойства или статуса, добавить который занимает ровно 5 секунд. И не важно, это будет на этапе разработки, или уже после внедрения. И ничего не сломается.

В платформу уже встроены все нужные аспекты взаимодействия, включая историю изменений, интеграцию, простой и гибкий жизненный цикл в конце концов, для каждого объекта, который нужен примерно везде, для любой разработки.

Если уже настроена интеграция и потребителю вдруг срочно понадобились новые или расширенные данные, не нужно срочно бежать и писать 100500 новых контроллеров, потому что все базовые стандартизиров��нные сервисы уже из коробки могут взаимодействовать с любым контентом. Потребитель может сразу прописать у себя новые сущности или новые условия поиска, не дожидаясь разработки. Конечно же, для особо "упоротых" внутренних потребителей, которые способны использовать только определённый формат API - приходится делать отдельные сервисы. Такое безусловно бывает, да, "в семье не без урода".

Всё, я выговорился... простите. Наболело.

Так продолжалось некоторое время, пока не закончились мешки с деньгами, которые оплачивали новую "дорогую" концепцию разработки. Теперь внутренние "заказчики" не хотят кормить стадо ИТшников и ждать по несколько месяцев, вспоминая, что раньше делали примерно то же самое, но быстрее.

Если сейчас, в вашей компании до сих пор кидают мешок денег в ИТишников, чтобы сделать очередную простенькую автоматизацию на 100 пользователей - могу вам только позавидовать.

Цели

Чтобы избежать недопонимания - НЕТ цели конкурировать с популярными фреймворками и сложными высоконагруженными разработками в высокотехнологичных областях ИТ, а также прочей заказной разработкой под конкретные требования. Здесь речь в первую очередь про обеспечение автоматизацией собственных нужд предприятия, с максимально низкими издержками на разработку таких решений.

Основная цель - это вернуть в строй МЕТОДОЛОГИЮ, когда разработка ведётся силами только команд из фуллстек разработчика и бизнес аналитика. Т.е. JMatrixPlatform должна обладать полным набором компонентов и подсистем, чтобы команда из двух человек смогла выполнить поставленную задачу. И это не "шапкозакидательство", это реальная работающая схема, которая успешно практиковалась в Enovia в течение долгих лет. При этом задача аналитика включает чёткое определение границ возможностей платформы: по максимуму использовать стандартные компоненты и при необходимости запустить разработку нового компонента, который в идеале может быть переиспользован в будущем.

По возможности, JMatrixPlatform должна обладать преемственностью и сохранять концепции, чтобы переход от Enovia был понятен и комфортен разработчикам. Естественно, там где это возможно - максимально улучшить и оптимизировать процесс кастомизации, обходя "детские" болячки старой платформы.

Философия

С целями вроде разобрались. И теперь затрону не менее значимый пункт - это Философия JMatrixPlatform. И тут трудно удержаться, не сказав что мне лично сильно импонирует философия Enovia, которая хоть и сильно устарела, но её взгляд на построение схемы модели данных, включая конфигурируемые UI компоненты - до сих пор вызывает восторг, то какой гибкой она может быть и одновременно простой! Столько лет прошло, а никто даже не смог приблизится к основам и принципам стандартизации, заложенным там. Когда при проектировании нового модуля, сама философия подсказывает сколько типов, атрибутов и связей нужно создать для взаимодействия с контентом. Всё просто "появляется" само собой. При проектировании нового решения на поверхность всплывают все ошибки аналитики, которые тут же исправляются, просто потому, что платформа не пытается поглотить сразу весь процесс, а заставляет раскладывать всё на маленькие детали, вплоть до кнопочек, из которых затем складывается вся конструкция. Если детали не подходят - это сразу видно. А с учётом того, что основное действующее лицо на сцене - это фуллстек, то на выходе получается более целостный модуль, лишённый распространённых проблем, которые возникают на границах БД, бэка и фронта. Вся платформа становится сама себе гайдом по стандартизации разработки, внутри которой создаются тысячи переиспользуемых деталек и остаётся только взять подходящие.

В идеале, хорошая платформа - душа и стандарт предприятия. Простая, понятная и гибкая, широкого спектра применения. Чтобы при возникновении необходимости некой новой автоматизации, все с теплом вспоминали: "У нас же есть платформа, на ней и сделаем!". Вы не поверите, но были времена, когда так говорили про Enovia.

В этом и заключается философия JMatrixPlatform: это не очередной фреймворк и не просто технология. Это способ организации труда (методология), которая возвращает разработчику главное - возможность думать, делать и сразу видеть результат.

Выбор стека

Не знаю почему, но на рынке до сих пор предлагают платформы на базе десктоп решений (привет TDMS, АсконЛоцман, 1C и т.д.). Наверно там есть какие-то аргументы для этого.

Я считаю что на сегодняшний день в этом направлении нет ничего лучше клиент-серверной архитектуры. Без болтовни, сразу к делу:

  1. Java, Spring Boot, Maven - это бэк, классика жанра.

  2. VueJS - фронт. Почему он? Был нужен простой фреймворк с низким уровнем входа. Понравилась его простота и философия.

  3. Postgresql - это база, база данных! Ещё рассматривалась nosql и мне попалась ravendb с полноценной поддержкой ACID. Крутил вертел её как только мог, и сбоку и сверху - не зашло. Как минимум потому, что в ней отсутствуют join'ы. А они обязательно нужны для построения сложных структур данных.

  4. MinIO - хранение файлов.

  5. Keycloak - авторизация и аутентификация.

  6. Apache Kafka - для обмена данными и интеграций.

  7. Redis - in memory кэш.

  8. Docker Compose - контейнеризация.

Нет ничего лучше кода

В прошлой статье абсолютно верно подметили, что кастомизация с помощью различных текстовых форматов (xml, json, yaml и различные узкоспециализированные языки) - боль и страдания. Нет ни единого сомнения, что использовать такой подход при создании JMatrixPlatform - выстрел себе в ногу. Я хоть и люблю Enovia, но этот проклятый механизм кастомизации mxUpdate - вершина маразма. Пусть он горит в аду.

Поскольку JMatrixPlatform - не что иное как свежий взгляд на уже морально устаревшую Enovia, как тогда взять полноценную концепцию построения модели данных Enovia и не утонуть в реализации чтения и валидации схем, при этом сохранить преемственность исходной платформы???

Я долго размышлял над этой поставленной перед собой задачей. Пока не пришло понимание - а зачем вообще конфигурировать эти промежуточные текстовые файлы, сложные в обращении, без зависимостей, без автокомплитов, без онлайн валидации и которые затем нужно всё равно превращать в ООП? И в этот момент напрашивается нестандартное, но логичное решение - сразу писать всё на java! Просто наследоваться от соответствующих JAttribute, JType, JRelationship и теперь IDE сама подскажет какие методы нужно имплементировать, проверит синтаксис, покажет зависимости и поможет написать код. Теперь посмотреть в каких типах используется атрибут, или в каких связях используется тип - секундное дело для IDE!

И возможно сейчас это покажется каким-то простым решением, но на старте разработки, в конце 2024 года - это решение выглядело неожиданным и одновременно перспективным. Мысли путались, а глаза боялись. Казалось, что текстовые форматы это хорошо, столько лет с ними прожили, не надо от них отказываться. Но решение в пользу java кастомизации было принято за пару ночей и понеслась разработка ядра.

На примере прошлой статьи, конфигурация типа Самосвал, с атрибутами VIN и Гос.номер, в JMatrixPlatform выглядит так:

@JModelPart
public class ATPDumpTruck extends JType {
  public static final ATPDumpTruck TYPE = new ATPDumpTruck();

  //атрибуты самосвала
  //напомню, что остальные базовые свойства уже присутствуют у всех объектов:
  //code, release, fullCode, title, fullTitle, description,
  //created, modified, author и т.д.
  @Override
  public Set<JAttribute> getAttributes() {
    return Set.of(
      VIN.ATTRIBUTE,
      RegNumber.ATTRIBUTE);
  }

  //ui компонент навигатора для просмотра объекта по ссылке /navigator?oid=UUID
  @Override
  public JMenu getNavigator() {
    return ANVDumpTrack.NAVIGATOR;
  }

  //действие при создании самосвала - сразу создаются 4 колеса
  @Override
  public void createAction(JContext ctx, UUID oid, JTriggerChanges changes) {
    JDomainObject jdoVehicle = new JDomainObject(oid);

    // String[] locations = { "FL", "FR", "RL", "RR" };
    for (IJDataTypeSimple location : ATRTireLocation.ATTRIBUTE.getChoices()) {
      JDomainObject jdoTire = new JDomainObject();
      //создаём новый объект колеса 
      jdoTire.create(ctx, ATPTire.TYPE, ALCTire.POLICY);
      
      
      JDomainConnection jdcTire = new JDomainConnection();
      //указываем расположение созданного колеса
      jdcTire.changes().setAttributeValue(ATRTireLocation.ATTRIBUTE, location.getDisplayValue());
      //создаём связь между самосвалом и колесом 
      jdcTire.create(ctx, ARSVehicleTire.RELATIONSHIP, jdoVehicle, jdoTire);
    }
  }
}

Т.е. теперь всё есть код! И нет ничего лучше кода. Не важно, какой компонент кастомизируется: запрос, форма или кнопка на панели инструментов - это всё пишется прямо на java. И это великолепно. Если интересно, под спойлером набросал ещё примеров. Можно пропустить.

Ещё примеры

А так выглядит реализация связи между Самосвалом и Колесом:

@JModelPart
public final class ARSVehicleTire extends JRelationship {
  public static final ARSVehicleTire RELATIONSHIP = new ARSVehicleTire();

  //разрешение связи: тип-тип, тип-связь, связь-тип, связь-связь
  @Override
  public JResolution getResolution() {
    return JResolution.TYPE_TYPE;
  }

  //перечисление разрешённых типов на from конце
  @Override
  public Set<JDataType> getFromDataTypes() {
    return Set.of(ATPVehicle.TYPE);
  }
  
  //перечисление разрешённых типов на to конце
  @Override
  public Set<JDataType> getToDataTypes() {
    return Set.of(ATPTire.TYPE);
  }

  //кардинальность from, т.е. сколько связей может быть создано на стороне from
  @Override
  public Cardinality getFromCardinality() {
    return Cardinality.ONE;
  }
  
  //кардинальность to, т.е. сколько связей может быть создано на стороне to
  @Override
  public Cardinality getToCardinality() {
    return Cardinality.MANY;
  }

  //запретить или разрешить создание ещё одной такой связи (для many-many)
  @Override
  public boolean preventDuplicates() {
    return true;
  }

  //атрибуты связи
  @Override
  public Set<JAttribute> getAttributes() {
    return Set.of(ATRTireLocation.ATTRIBUTE);//расположение колеса FL, FR и т.д.
  }
}

Форма создания самосвала:

@JModelPart
public final class AFMDumpTruckCreate extends JForm {
  public final static AFMDumpTruckCreate FORM = new AFMDumpTruckCreate();
  
  @Override
  public JFields getFields(JContext ctx, JRequestParams requestParams, JRequestBody requestBody) {
    JFields fields = new JFields();

    fields.put("title", JField.builder()
         //имя поля
        .setLabel(JBTitle.BASIC.getLabel(ctx))
         //редактируемое
        .setEditable(true)
         //обязательное
        .setRequired(true)
         //отображается как простой textbox
        .setRenderComponent(JField.R.JTextbox));

    fields.put("vin", JField.builder()
        .setLabel(ctx.getMessage("mx.Demo.Label.VIN"))
        .setEditable(true)
        .setRequired(true)
        .setRenderComponent(JField.R.JTextbox));

    fields.put("regNumber", JField.builder()
        .setLabel(ctx.getMessage("mx.Demo.Label.RegNumber"))
        .setEditable(true)
        .setRequired(true)
        .setRenderComponent(JField.R.JTextbox));

    //ответственный за состояние самосвала
    fields.put("responsiblePerson", JField.builder()
        .setLabel(ARSVehicleResponsible.RELATIONSHIP.getLabel(ctx))
         //редактируемое поле
        .setEditable(true)
         //устанавливаем выбор из справочника       
        .setRenderComponent(JField.R.JSmartChoice)
         //указываем запрос для поиска объектов справочника
        .putSetting(JRequestParams.INQUIRY, AIActivePersonsSearch.INQUIRY)
         //устанавливаем выбор только одного объекта из справочника
        .putSetting(JRequestParams.ROW_SELECTION, RowSelection.SINGLE.name())
         //показывать кнопку очистки/сброса значения
        .setShowClear(true));

    return fields;
  }

  //логика обновления значений формы
  //т.е. выполняется именно этот метод, когда пользователь жмёт Сохранить
  //на конкретной форме
  @Override
  public void commitValues(JContext ctx, JRequestParams requestParams, JRequestBody requestBody) {
    JDomainObject domainObject = new JDomainObject(requestParams.getRequiredOID());

    JFields fields = requestBody.getSubmitFields();

    fields.ifSingleInputChanged("title", input -> {
      domainObject.changes().setTitle(input.getValue());
    });

    fields.ifSingleInputChanged("vin", input -> {
      domainObject.changes().setAttributeValue(VIN.ATTRIBUTE, input.getValue());
    });

    fields.ifSingleInputChanged("regNumber", input -> {
      domainObject.changes().setAttributeValue(RegNumber.ATTRIBUTE, input.getValue());
    });

    requestBody.getSubmitFields().reConnect(ctx, "responsiblePerson", domainObject,
        ARSVehicleResponsible.RELATIONSHIP, JDirection.FROM);

    //вносим все накопленные изменения в БД
    domainObject.applyChanges(ctx);
  }
}

Запрос для справочника шин:

@JModelPart
public final class AIQTiresBrandAll extends JInquiry {
  public static final AIQTiresBrandAll INQUIRY = new AIQTiresBrandAll();

  @Override
  public JQueryObjectResult getQueryResult(JContext ctx, JRequestParams requestParams, JRequestBody requestBody) {
    ////указываем какой тип искать, включая подтипы если они есть
    JQueryObject objectQuery = JQueryObject.builder(Set.of(ATPTireBrand.TYPE), true)
        .setMapper(JSLTireBrand.class)//какие свойства извлечь
        .setPageSize(500)//кол-во объектов на страницу
        .setLimit(5000)//ограничить поиск
        .setWhere(JSearch.builder()
            .and(JBPolicyState.BASIC, JQ.NEQ, ALCReference.Archive.STATE)
            .and(JBOwner.BASIC, JQ.NEQ, JCache.getPersonUUIDByLogin("jadmin"))
             //и т.д. любые условия
            .build())
        .build();

    return JDomainObject.query(ctx, objectQuery);
  }
}

Если раньше для извлечения свойств нужно было писать текстом select, а затем извлекать значения из Map:

  • name

  • attribute[Email]

  • to[Member].from.id

  • to[Member].from.attribute[SapId]

  • to[Member].from.to[Project].from.attribute[SapId]

  • и т.д.

То теперь select легко выполняется через промежуточный DTO. Также обратите внимание на извлечение свойств связанных объектов, любой глубины (для сложных структур):

@NoArgsConstructor
@Getter
@Setter
public class JSLDumpTruckWithTires extends JSLObject {
  
  @JSelectBasic(JBCode.class)
  private String code;
  
  @JSelectAttribute(VIN.class)
  private String vin;
  
  @JSelectAttribute(RegNumber.class)
  private String regNumber;
  
  @JSelectToRelated(ARSVehicleTire.class)
  private List<ConnectionTire> tires;

  @NoArgsConstructor
  @Getter
  @Setter
  public static class ConnectionTire extends JSLRelatedConnection<JSLTire> {
    @JSelectAttribute(ATRTireLocation.class)
    private String location;
  }

  @NoArgsConstructor
  @Getter
  @Setter
  public static class JSLTire extends JSLObject {
    @JSelectBasic(JBCode.class)
    private String code;
    @JSelectBasic(JBRelease.class)
    private String release;
  
    @JSelectAttribute(ATRTireInstall.class)
    private Instant firstInstall;
    @JSelectAttribute(ATRTireReplacementPlan.class)
    private Instant replacementPlan;
    @JSelectAttribute(ATRTireReplacementFact.class)
    private Instant replacementFact;
    @JSelectAttribute(ATRTireReplacementReason.class)
    private String replacementReason;
  
    @JSelectToRelated(ARSTireBrand.class)
    private List<ConnectionBrand> tireBrand;

  }

  @NoArgsConstructor
  @Getter
  @Setter
  public static class ConnectionBrand extends JSLRelatedConnection<JSLTireBrand> {
    @JSelectAttribute(ATRTireLocation.class)
    private String location;
  }  

  @NoArgsConstructor
  @Getter
  @Setter
  public static class JSLTireBrand extends JSLObject {
  
    @JSelectBasic(JBCode.class)
    private String code;
    @JSelectBasic(JBTitle.class)
    private String title;
  
    @JSelectAttribute(ATRTireAcceptablePressure.class)
    private JDataTypeValue.JRealRange pressure;
  
  }  
}

Пример использования:

JSLDumpTruckWithTires dumpTruck = new JDomainObject(oid).select(ctx, JSLDumpTruckWithTires.class);

System.out.println(dumpTruck.getCode());
System.out.println(dumpTruck.getVin());
System.out.println(dumpTruck.getTires().get(0).getFirstInstall());
System.out.println(dumpTruck.getTires().get(0).getTireBrand().get(0).getTitle());
//и т.д.

Т.е. в любой момент времени можно извлекать любые свойства объектов, в том числе любые свойства любых связанных объектов. Если нужно только два свойства - JMatrixPlatform извлечёт только два. И не будет тащить всю цепочку связей и их свойств, если вы об этом не попросите.

Приложение "рулит" данными

Но на про��лой "инновации" (где всё есть код) я не остановился. Оставалось решить ещё одну главную проблему. Enovia хранит всю кастомизацию в БД! И это боль. У них было много причин сделать именно так, но также идти по этой скользкой тропинке не было никакого желания. При простых расчётах, повторить такое заняло бы около года разработки.

Что делать? В те же пару ночей также пришло и понимание того, что всю кастомизацию нужно оставить на уровне приложения в JVM, а БД будет хранить только краткую метаинформацию. Т.е. когда JMatrixPlatform извлекает объект из БД, вместе с ним извлекаются метки, например что это тип "ATPDumpTruck" и статус "ALCDumpTruck.New", далее в рантайме по статусу проверяется возможность доступа к объекту, если решение положительное, далее извлекается нужная информация. Также и перед редактированием объекта - сначала извлекается статус, в рантайме проверяется доступ к modify, если решение положительное - все изменения вносятся в БД, иначе ошибка доступа.

Если БД теперь ничего не знает о текущем пользователе и доступах, как теперь будет работать поиск объектов, с учётом статусно-ролевой модели? Это же главная киллер-фича Enovia!

Проще простого. Когда производится поиск конкретных типов, JMatrixPlatform в рантайме получает все возможные ЖЦ и статусы для этих типов, проверяет доступы на каждом статусе и добавляет доступные для чтения статусы в SQL запрос. Например, если текущий пользователь может видеть только "Опубликованные" и "Архивные" документы, то при поиске типа Documents, в запрос автоматически будет добавлено условие state in ('Published', 'Archived'). Это очень простая и эффективная операция проверки доступа. А для администратора эти проверки вообще не выполняются.

Грузовое шасси

Не пугайтесь заголовка, это не другая статья, вы всё ещё здесь. Я попробую показать "на пальцах", в чём принципиальное отличие классической современной разработки (например JPA + UI) от платформенного подхода. И вспомнил про грузовые шасси. Аналогия простая, но, кажется, очень точная.

Для изготовления автомобильной крановой установки, никто не делает шасси к ней с нуля, а использует готовую платформу (например Камаз или Volvo), которая обеспечивает весь готовый механизм бесперебойной работой. Нет необходимости каждый раз проектировать кабину, двигатель, руль, трансмиссию, подвеску и обеспечивать взаимодействие между ними. Это просто экономически нецелесообразно. Платформа поставляется готовым продуктом, в котором всё гарантированно работает, и главное, работает ровно так, как этого ожидает изготовитель кранов. Все органы управления стандартизированы, просты и понятны, присутствуют все необходимые приборы и показатели, точки подключения для различных внешних установок, включая вал отбора мощности и т.д. Тем более что для каждой области уже есть подходящая платформа со своими особенностями, достаточно просто выбрать. Но вместо этого, каждый раз, используя условный JPA и UI начинают изобретать очередную кабину, двигатель, подвеску и делают это так криво, что последующие "водители" просто мучаются и не хотят водить это ведро: горловина бака под днищем, джойстик вместо руля, стеклоподъёмники в бардачке. Платформа - это не замена JPA + UI по отдельности, это урове��ь выше - готовый продукт, чтобы возить крановую установку. И успешным производителем кранов является тот, кто не тратит время на проектирование не свойственных ему компонентов.

При этом, грузовая платформа прекрасно модифицируется. Можно установить внедорожные шины, дооснастить турбиной, прицепом или даже летающим дроном (для разведки местности), органы управления которым, аккуратно размещаются на приборной панели кабины (аналогия подключения внешнего микросервиса).

И только если нужен космический аппарат или быстрый гоночный автомобиль для трека, вот тогда я беру "низкоуровневые" фреймворки. В данном случае грузовая платформа конечно же может выехать на трек, но именно в спортивных гонках она никогда не победит.

Заключение

На сегодняшний день JMatrixPlatform уже прошла стадию MVP. Реализовано ядро и все необходимые компоненты и подсистемы для успешного и комфортного использования, включая все базовые конфигурируемые UI компоненты. Созданы 3 демонстрационных модуля для тестирования и проверки возможностей, приближенных к реальному применению:

  1. Документооборот, с ревизиями документов, включая автоматизацию, в которой титул, чертёж, спецификация и смета объединяются в один итоговый pdf для сдачи в архив.

  2. Менеджер колёс самосвала, с ручной фиксацией причины и даты замены каждой из шин.

  3. Архив документации, с ручным размещением ссылок непосредственно на сборочном pdf чертеже, для быстрой навигации.

И если первые два используют абсолютно стандартные компоненты, т.е. сделаны только с помощью конфигурации и дополнительного кода для бизнес-логики, то последний модуль потребовал разработки дополнительного UI компонента для просмотра и редактирования pdf, с подключением внешней библиотеки pdfjs.

Как видите, отсутствие какого-либо компонента не является препятствием для использования платформы. Фуллстек подход обеспечивает полноценную разработку необходимых компонентов "под ключ".

Скриншоты
Демо-комплект, с вложенными документами
Демо-комплект, с вложенными документами
Демо-БелАЗ с порталом шин и историей замены, где номер замены - полноценная версия объекта колеса/шины.
Демо-БелАЗ с порталом шин и историей замены, где номер замены - полноценная версия объекта колеса/шины.
Демо архивного документа со сборочным pdf чертежом и ссылками на нём, расставляемые перетаскиванием из таблицы узлов.
Демо архивного документа со сборочным pdf чертежом и ссылками на нём, расставляемые перетаскиванием из таблицы узлов.

Все демо-модули поставляются "в коробке" и доступны в качестве обучающих материалов для начала новой разработки.

На сегодня у меня всё. Применяйте платформы там, где это целесообразно, а где нецелесообразно - не применяйте! :-)