Как стать автором
Обновить

Передовые технологии на службе СЭД

Время на прочтение21 мин
Количество просмотров4.7K

Аннотация

В статье монографически раскрываются современные аспекты разработки документо-ориентированных систем основанных на собственном опыте. Все исследования и реализация технологий последовательно выполнены в ряде проектов на протяжении последних 3-х лет, где частично или полностью использовался представленный подход. Пошагово показан путь создания высоконагруженной СЭД и одновременно формирования в рамках полученной платформы многофункциональной CRM.

Стратегия разработки подчинена парадигме: если технологии позволяют не расширять инфраструктуру при допустимом ущербе качества с сохранением стабильности и доступности – инфраструктура не расширяется. Данная парадигма минимизирует вероятные точки отказа, уменьшает стоимость разработки проекта и в итоге стоимость инфраструктуры.

Введение

CRM, Help Desk, Service Desk достаточно простые и входят в одно подмножество характеризующиеся общими свойствами, включая свойство прямой реализации требований и соответственно низкой масштабируемости на уровне бизнес-требований в рамках бюджета. Обычно СЭД в контексте данных систем не рассматривается из-за дополнительных ресурсов для очередной «серебряной пули себе в ногу» и конечно различной степени боли инвариантной экспектации.

В силу сказанного, большинство СЭД разработанные более 5 лет назад в основном являются неуклюжим конгломератом устаревших технологий от которых почти невозможно отказаться из-за кажущихся значительных расходов на разработку нового СЭД с текущими трендами. Таким образом, возникает общая тенденция продолжать строить сверху нагромождения из современных подходов, что действительно удешевляет поддержку и доработку в ущерб масштабируемости. В результате чего, разработчикам приходится становиться узкопрофилированными специалистами сложных систем, что и произошло на примере SAP, Siebel и т.д.

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

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

Приведу на мой взгляд современные проблемы разработки:

  1. Чем больше типов обрабатываемых данных, тем дольше и сложнее разработка на каждом этапе – данная проблема связана с использованием по привычке устаревших технологий, к которым можно отнести и классические реляционные базы данных, концепция которых была принята ещё в далёком 1971 году и изначально адаптирована к данным со статичной структурой;

  2. Архитекторы при проектировании систем в основном основываются на проверенном годами положительном опыте и текущих знаниях сотрудников, в итоге, через несколько месяцев получают то, что можно реализовать за месяц новыми технологиями с большим функционалом и гораздо меньшим количеством сотрудников;

  3. Классические инструменты полнотекстового поиска, глубокого анализа и построения кубов загружают исходные данные в собственные хранилища, что приводит к многократному дублированию баз данных, снижении стабильности и росту стоимости владения.

Решение перечисленных проблем мне видится только в передовых эволюционных подходах, отвечающие современным требованиям, включая оперативную адаптацию под постоянно меняющиеся внешние факторы.

Предлагаемые в статье технологии и подходы позволяют значительно уменьшить и упростить исходный код, и наконец, унифицировать подход к любому типу данных, включая визуализацию и построение отчётов. Сведя в итоге стоимость разработки и владения в разы по сравнению с «привычными» подходами.

Предметная область

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

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

Каждый участник платформы имеет собственные внутрикорпоративные стандарты ведения электронного документооборота и при отправке документ может быть сканом в виде рисунка TIFF, PNG, JPG, документом Word, Excel, ODT, ODS, PDF с различными версиями, файлами TXT или CSV и презентациями PPT или ODP.

Бизнес-процессы участников описаны в нотации BPMN 2.0 и могут следовать стандартам ISO 9000, ISO 20000, ITIL, COBIT с применением инструментов BPM от различных компаний.

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

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

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

Технологии

База данных: PostgreSQL

В разное время Oracle и Uber (Uber отказался от PostgreSQL в пользу MySQL от Oracle) опубликовали статьи на тему «Почему не стоит использовать PostgreSQL» – следует поблагодарить ребят за кропотливую работу и заботливые напоминания, сделав прекрасный подарок в виде необходимого и достаточного чек-листа взросления PostgreSQL.

Вот чек-лист на мой взгляд зрелости современной PostgreSQL:

  • выбор и использование именно PostgreSQL обусловлено крайне широкой поддержкой JSON с соответствующей реализацией большого количества функций и расширений, кроме того, PostgreSQL позволяет выстраивать внешние связи между JSON-данными, что например не умеет журнальный MongoDB;

  • PostgreSQL выполняет запросы с полями типа JSON быстрее Oracle до 5 раз и быстрее MongoDB до 10 раз – что ставит PostgreSQL в один ряд с No-SQL базами данных, а учитывая возможность PostgreSQL также строить высокоскоростные key-value (hstore) хранилища с богатым функционалом и поддержкой журнала транзакций (чего нет в Redis), то и первым в ряду;

  • если сравнивать скорость выполнения запросов реляционных таблиц и JSON с тем же набором данных в самом PostgreSQL, то разница составит до 2000 раз, а разница выгрузки в контейнер сервлетов результата JSON-запросов составит более 100 мс на каждые 100 тыс. записей, также размер JSON-хранилища на диске меньше до 2 раз и до 8 раз меньше размер индексов при больших массивах данных;

  • подключив видеопамять к серверу производительность PostgreSQL по всем типам запросов автоматически увеличится в среднем на 17%, конечно, также не стоит использовать конфигурационный файл по дефолту, т.к. его адаптация под станцию и ОС – увеличит производительность до 3 раз;

  • наличие активно развивающегося расширения Apache MadLib, разработанный учёными университета Berkeley совместно с инженерами корпорации Dell EMC (предоставили для тестирования терабайты финансовой статистики за десятилетия), где реализованы все известные математические алгоритмы машинного обучения для работы с данными в реальном времени непосредственно в базе данных;

  • появился скоростной индекс RUM, который позволяет строить качественный релевантный мультиязычный полнотекстовый поиск с синтаксисом поискового web-запроса по таблицам с десятками миллионов записей за ~400 мс обычным сканированием индекса за счёт избыточной индексации без использования оперативной памяти – что позволяет исключить кластер ElasticSearch с огромными требованиями к памяти;

  • PostgreSQL умеет также в реальном времени строить куб в параллельных потоках – что считается продвинутым на рынке OLAP и с явным преимуществом в виде: уровней абстракций (рекурсивные кубы), декларативного языка запросов и сравнительно более высокой точности;

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

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

data class JObject : Serializable {
    var id: UUID
    var jtree: JsonNode
}

Многие системы на определённом этапе переходят в разряд высоконагруженных, что часто ведёт к их переписыванию и первым порядком идёт отказ от использования Hibernate как от бутылочного горлышка, что ускоряет запросы к БД от 10 раз и выше, а операции вставки до 5 раз. И конечно, в этом деле, использование подхода No-SQL от PostgreSQL играет ключевую роль.

Движок бизнес-процессов: Activiti

Безусловными лидерами корпоративного рынка движков бизнес-процессов (BPM) являются коммерческие (проприетарные) движки Lombardi Teamworks, Pegasystems SmartBPM, Savvion Business Manager, Oracle BPM Suite, Intalio BPMS, TIBCO Business Studio включающие в себя мощные инструменты имитационного моделирования для проигрывания различных сценариев поведения бизнес-моделей и аналитические средства мониторинга для выявления узких мест.

Выбор Activiti BPM по следующим причинам: Activiti создал программист, в своё время, разработавший jBPM для компании JBoss, также, в разработке продукта участвуют программисты из Spring Source – настолько сильной командой и компетенциями не может похвастаться ни один из движков BPM.

Разработчики Activiti наконец сделали свой продукт высоконагруженным и начиная с версии 7.1.0-М17 прирост производительности составил 10 раз. Инфраструктура вокруг Activiti слабая, отсутствуют штатные средства мониторинга, но в своё время был анонсирован в рамках проекта Activiti Cloud web-редактор бизнес-процессов, который можно интегрировать в СЭД и кустомизировать.

Бэкенд: Reactive Spring Cloud

Для организации взаимодействия подсистем (микросервисов) между собой классически используется архитектурный шаблон сервис-ориентированной архитектуры (SOA). И в соответствии с SOA появилась новая концепция шины данных Talend ESB, основанная на интеграционном фреймворке Apache Camel. Полностью поддержанная Activiti BPM и позволяющая напрямую интегрироваться в ESB-шину с возможностью использовать непосредственно в бизнес-процессах технических шагов Talend ESB. Такая возможность из коробки отсутствует в большинстве BPM-движках.

Talend ESB также базируется на Apache Karaf, что позволяет динамически загружать и выгружать Java-классы, в следствии чего, бизнес-процессы Activiti BPM становятся динамичными. Talend публикует целую линейку технологий для работы с данными: MDM, Big Data ETL, Data Quality – каждая из которых имеет визуальные средства моделирования и вся линейка интегрируема друг в друга.

Вот, как бы, вся мощь империи, но ESB-шины при всей своей широкой функциональности и гибкости имеют свои архитектурные рамки, за которые выйти без нарушения внутренней логики невозможно. И таким образом, эволюционно, появился Spring Cloud, предоставляя максимальную свободу и представляя собой ESB-шину без рамок, при этом, унаследовав всю терминологию и базовые принципы ESB-шин такие как: Discovery, Gateway, Config Server, Secrets и т.д.

Шли годы и современные требования заявили о необходимости поддержки высокой нагрузки, так появился на свет Reactive Spring Cloud, в основе которого находится сервер Reactor Netty. Netty быстрее Tomcat в 10 раз и NodeJS в 3 раза, что позволяет разработчикам создавать реактивные системы корпоративного уровня со всей инфраструктурой отказоустойчивости и масштабируемости Spring Cloud.

Более нет необходимости в разбалансировке 10 подов сервиса Spring на базе Tomcat, когда достаточно 2 подов с Netty. Отдельному инстансу с Netty выделяется памяти значительно меньше чем Tomcat. В этом и суть новых технологий, когда стоимость разработки и владения уменьшаются в разы.

Результатом реактивной разработки являются неблокируемые приложения с асинхронными потоками данных и более эффективным использованием процессорного времени. Ускорение работы реактивных приложений на первый взгляд незаметен и составляет сотые доли секунды, но в высоконагруженных системах даже 10 мс суммируется. Тут надо учитывать, что высокие нагрузки присутствуют на каждом из этапов обработки потока данных.

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

Для реактивной разработки используется микрофреймворк WebFlux на базе библиотеки Project Reactor, более продвинутой чем RxJava. Асинхронный код лаконичен и если писать ещё на Kotlin, то в итоге код становится в несколько раз меньше от аналогичного на классической Java, т.е. во много раз читабельнее. На Kotlin проще и продуктивнее работать. При работе с WebFlux необходимо учитывать рецепты от команды SberDevices.

Неотъемлемой частью WebFlux является реактивная модель, основанная на управляемых событиях Event-Driven (EDA) с использованием одного из брокеров сообщений. В качестве брокера обычно выбирают Kafka из-за скорости и гарантии доставки. Kafka быстрее ActiveMQ и RabbitMQ в 5 раз. Но выбор пал на NATS из-за отсутствия необходимости в дополнительном сервере ZooKeeper.

NATS быстрее Kafka в 2 раза. Скорость NATS в особенности реализации, когда сообщения доставляются напрямую от отправителя к получателю. NATS прост, из коробки сжимает с gRPC все сообщения, естественно интегрируется в реактивный стиль за счёт поддержки pub/sub/stream.

Гарантия доставки NATS построена на действительно революционной подсистеме JetStream с правилом at-least-once delivery (доставка хотя бы один раз), оптимизированной для безопасной обработки миллиардов сообщений. JetStream обеспечивает несколько способов получения данных от одного источника – это очень похоже на распределённое реплицируемое блочное устройство (DRBD) с поддержкой multi-path (несколько каналов связи между узлами), входящее в ядро Linux и применяемое для построения отказоустойчивых кластерных систем на ОС Linux.

Фронтенд: Angular

Безусловно, Angular имеет сравнительно высокий уровень порог вхождения, но выбор сделан из-за web-редактора бизнес-процессов Activiti, который реализован на Angular.

Есть множество генераторов форм на Angular и большинство весьма хороши, концепция современной СЭД подразумевает хранение всех интерфейсов бизнес-процессов и любых форм в виде JSON в базе данных, которые можно отредактировать в визуальном редакторе без привлечения программиста – присутствуют множество платных редакторов, но свой можно реализовать за неделю.

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

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

Архитектура

Шаги реализации

1) Базовые таблицы БД:

Схемы форм Angular хранятся соответственно в form. Осмысление папок тривиальна. В поле jtree хранятся помимо name и description, любые уникальные атрибуты записи и как видно, присутствует во всех таблицах. Все объекты сохраняются в одну таблицу jobject, в которой записи физически распределяются по наследуемым таблицам по типу папки. Все объекты могут ссылаться друг на друга по несколько раз и с разными типами связей (структура каждого типа уникальна).

Скрипт создания таблиц
CREATE TABLE public.form
(
    id         uuid                     DEFAULT uuid_generate_v1mc() NOT NULL PRIMARY KEY,
    jtree      jsonb                                                 NOT NULL,
    created_at timestamp with time zone DEFAULT timezone('utc'::text, CURRENT_TIMESTAMP)
);

CREATE TABLE public.jfolder
(
    id         uuid                     DEFAULT uuid_generate_v1mc() NOT NULL PRIMARY KEY,
    code       text                                                  NOT NULL,
    jtree      jsonb                                                 NOT NULL,
    form_id    uuid REFERENCES form (id),
    parent_id  uuid REFERENCES jfolder (id),
    created_at timestamp with time zone DEFAULT timezone('utc'::text, CURRENT_TIMESTAMP)
);

CREATE TABLE public.jobject
(
    id         uuid                     DEFAULT uuid_generate_v1mc() NOT NULL,
    jtree      jsonb                                                 NOT NULL,
    jfolder_id uuid                                                  NOT NULL REFERENCES jfolder (id),
    created_at timestamp with time zone DEFAULT timezone('utc'::text, CURRENT_TIMESTAMP),
    tsv        tsvector,
    PRIMARY KEY (id, jfolder_id)
) PARTITION BY LIST (jfolder_id);

CREATE TABLE public.jobject_jobject
(
    id        uuid DEFAULT uuid_generate_v1mc() NOT NULL PRIMARY KEY,
    left_id   uuid                              NOT NULL,
    right_id  uuid                              NOT NULL,
    jtree     jsonb,
    parent_id uuid REFERENCES jobject_jobject (id)
);

INSERT INTO public.jfolder(code, jtree) VALUES ('document', '{"name": "Документы"}');
INSERT INTO public.jfolder(code, jtree) VALUES ('organization', '{"name": "Организации"}');
INSERT INTO public.jfolder(code, jtree) VALUES ('employee', '{"name": "Сотрудники"}');
INSERT INTO public.jfolder(code, jtree) VALUES ('file', '{"name": "Файлы"}');
INSERT INTO public.jfolder(code, jtree) VALUES ('type', '{"name": "Типы документов"}');

CREATE FUNCTION public.jfolder_by_code(p_code text) RETURNS uuid
    LANGUAGE plpgsql AS
$$
declare
    jfolder_id uuid;
BEGIN
    SELECT id into jfolder_id FROM jfolder WHERE code = p_code;
    RETURN jfolder_id;
END;
$$;

CREATE TABLE jobject_document PARTITION OF jobject FOR VALUES IN (jfolder_by_code('document'));
CREATE TABLE jobject_organization PARTITION OF jobject FOR VALUES IN (jfolder_by_code('organization'));
CREATE TABLE jobject_employee PARTITION OF jobject FOR VALUES IN (jfolder_by_code('employee'));
CREATE TABLE jobject_file PARTITION OF jobject FOR VALUES IN (jfolder_by_code('file'));
CREATE TABLE jobject_type PARTITION OF jobject FOR VALUES IN (jfolder_by_code('type'));

INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('type'), 'email', '{"name": "Почта"}');
INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('type'), 'sms', '{"name": "SMS"}');

INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('email'), 'email_template', '{"name": "Шаблон"}');
INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('email'), 'email_draft', '{"name": "Черновик"}');
INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('sms'), 'sms_personal', '{"name": "Персонально"}');
INSERT INTO public.jfolder(parent_id, code, jtree) VALUES (jfolder_by_code('sms'), 'sms_all', '{"name": "Рассылка всем"}');

CREATE OR REPLACE FUNCTION jtree(p_parent_id uuid) RETURNS SETOF jsonb AS
$$
BEGIN
    RETURN QUERY EXECUTE '
        select case
            when count(x) > 0 then
                jsonb_build_object(''id'', t.id, ''name'', t.jtree ->> ''name'', ''items'', jsonb_agg(f.x))
            else
                jsonb_build_object(''id'', t.id, ''name'', t.jtree ->> ''name'')
            end jtree
        from jfolder t left join jtree(t.id) as f(x) on true
        where t.parent_id = $1 or (t.parent_id is null and $1 is null)
        group by t.id, t.jtree ->> ''name''' 
        USING $1;
END
$$ LANGUAGE plpgsql
   SECURITY DEFINER
   IMMUTABLE;

Запрос:

select distinct jtree(null) from jfolder 

позволяет получить готовое дерево папок для фронта в виде:

{
  "id": "e0431130-f9da-11eb-992c-b3e556fc235b",
  "name": "Типы документов",
  "items": [
    {
      "id": "7e48e4bc-032e-11ec-9d20-7b4a76779782",
      "name": "Почта",
      "items": [
        {
          "id": "c5695f46-0330-11ec-9d20-f7e01673c7ab",
          "name": "Шаблон"
        },
        {
          "id": "fc9bf44e-0332-11ec-9d20-2fc71c567d6d",
          "name": "Черновик"
        },
      ]
    },
    {
      "id": "7e48e4bd-032e-11ec-9d20-bb8fd66d875c",
      "name": "SMS",
      "items": [
        {
          "id": "fc9bf450-0332-11ec-9d20-639360800cea",
          "name": "Рассылка"
        },
        {
          "id": "c5695f48-0330-11ec-9d20-9f0a5c2f030f",
          "name": "Персонально"
        },
      ]
    }
  ]
}

2) Базовые принципы бэкенда:

Для взаимодействия с базами данных используется реактивный Spring Data, в которой вместо Hibernate располагается R2DBC с поддержкой реактивных драйверов баз данных и являющийся аналогом JDBC, но с чуть большей скоростью выполнения запросов.

Понятия Entity-прокси больше не существует, реактивный R2dbcRepository оперирует обычными классами и маппит результат через FastMethodInvoker, сравнимый по скорости с прямым доступом.

Возможности реактивного репозитория Spring Data при наследовании от интерфейса R2dbcRepository явно не соответствует современным требованиям, перепишем его на уровне спецификации в отдельную библиотеку чтобы:

  • перегрузить функции репозитория на дополнительный параметр типа Dsl – этот подход немножко напоминает https://github.com/querydsl/querydsl, но значительно удобнее в использовании и весьма функциональнее;

  • отключить обязательность присутствия в классах R2DBC-аннотаций, т.е. использовать обычные классы как есть – через Dsl можно легко объединять таблицы.

Интерфейс модифицированного репозитория:

interface R2dbcRepository<T, ID> : ReactiveCrudRepository<T, ID> {
    fun findOne(dsl: Dsl): Mono<T>
    fun findAll(dsl: Dsl): Flux<T>
    fun delete(dsl: Dsl): Mono<Integer>
    fun listener(): Flux<Notification>
    fun saveBatch(models: Iterable<S>): Flux<Result>
}

В Dsl можно выстроить предикаты всех видов по любому полю или полям связанных таблиц, также при необходимости, можно перечислить поля в результате:

Dsl.create()
   .equals("hobby", "Konami")
   .isTrue("isMonicStyle")
   .isNull("name)
   .fields("age", "sisterTable.name", "jtree.hobby.description")

где после выполнения запроса будут заполнены поля результирующей модели: age, name, description.

Метод listener позволяет подписаться на любые изменения в таблице и в реальном времени получать уведомления прямо из базы данных:

dslRepository.listener()
          .onBackpressureLatest()
          .concatMap { notification ->
              val json = notification.parameter.toJsonNode()
              if (json["operation"].asText() == "INSERT") {
                  info("database event: $json")
              }
          }

Dsl поддерживает релевантный полнотекстовый поиск по таблице в которой используется по умолчанию поле tsv (можно указать любое имя):

dslRepository.findAll(
  Dsl.create().fts("cool|pencil").equals("type", 1L).pageable(0,20))

Метод saveBatch позволяет сохранять любые типы объектов в коллекции не привязанные к модели репозитория, т.к. операция носит массовый характер:

dslRepository.saveBatch(listOf(cool1, cool2, pencil1, pencil2))

Основная идея Dsl – сократить время разработки через максимальную простоту работы с БД, включая формирование на фронтенде всех возможных критерий без внесения изменений в бэкенд:

localhost:8080/items?query= shops.type==mega, name~~biggest, price>=100 &
fields=id, name & page=0 & size=20 & sort=itemType:asc, createdAt:desc

по которой генерируется SQL:

select id, name from items join shops on items.shop_id = shops.id 
where shops.type='mega' and name like '%biggest%' and price >= 100 
order by item_type asc, created_at desc limit 20 offset 0

Как это показано в примере, Dsl умеет спускаться через связанные таблицы и выстраивать объединения. Также, можно работать напрямую с клиентом базы данных:

val dsl = DslUtils.getCriteriaBy(Dsl.create().id(100L), JObject::class)
client.select().from(JObject::class).matching(dsl).fetch().one()

Dsl всегда формирует запрос через Criteria Builder, что исключает SQL-инъекции. Исходный код рабочей версии реактивной DslRepository находится на GitHub, также библиотека опубликована в Maven Central:

<dependency>
  <groupId>io.github.sevenparadigms</groupId>
  <artifactId>spring-data-r2dbc-dsl</artifactId>
  <version>4.5.23</version>
</dependency>

В данной версии Dsl внедрено непосредственно в Spring Data, т.е. её надо подключать взамен библиотеки spring-data-r2dbc. Десятки тестов библиотеки адаптированы под функционал Dsl.

Для дополнительного удобства и возможности использования Reactive Feign Client – была вынесена модель Dsl в отдельную библиотеку:

<dependency>
  <groupId>io.github.sevenparadigms</groupId>
  <artifactId>spring-data-r2dbc-dsl-common</artifactId>
  <version>4.5.23</version>
</dependency>

Все аспекты авторизации вынесли из Spring Security во внешние SSO-сервера. По рекомендации команды Spring следует использовать Keycloak, который по умолчанию используется также web-редактором бизнес-процессов Activiti. Необходимо учитывать низкую производительность сервера Keycloak при одновременной работе более 400 организаций.

Расширим реактивную Spring Security до поддержки реактивной модели безопасности ABAC, которая через совокупность правил умеет также строить гибкую и детальную политику безопасности.

ABAC также зафиксирует все разрешённые Dsl-запросы по каждому методу REST-контроллера. Зафиксировать все разрешённые Dsl-запросы у каждого метода не подключая Spring Security можно через имплементацию интерфейсов ConstraintValidator или Validator. Или воспользоваться удобной библиотекой:

@GetMapping
@ValidateParam(value = Check.Custom,
               argName = "dsl.query", express = "dslRegexValidator")
fun getItem(dsl: Dsl)

Создание правила «Только инспектор может просматривать все документы своего отдела»:

insert into abac_rule (name, description, type, target, condition)
values ('Инспектор видит все документы и только своего отдела',
        'может просматривать только те документы, которые созданы в отделе',
        'Document',
        'action == ''show department documents''',
        'authUser.role == ''inspector'' 
         and authUser.departmentId == domainObject.departmentId');

Применение правила ABAC в аннотации над методом:

@PreAuthorize("rule('connect only from department') 
              and rule('show department documents')")

В рамках создания современной СЭД бэкенд мало знает о передаваемых данных и в основном служит транспортом между фронтом и базой данных, обеспечивая прозрачную валидацию структуры передаваемых данных согласно сохраненным в БД схемам форм Angular и безопасность по модели ABAC для контроля доступа на основе совокупности правил.

@PostMapping
fun save(@RequestBody jobject: JObject) = objectService.save(jobject)

@GetMapping(value = ["{jfolderId}"])
fun findAll(@PathVariable jfolderId: UUID, dsl: Dsl) 
                                   = objectService.findAll(jfolderId, dsl)

Всего два метода контроллера могут обеспечить все потребности фронтенда при работе с базой данных. Бэкенд при этом знает только об абстрактной таблице jobject, от которой могут быть унаследованы до нескольких сотен реальных. Функция add принимает данные с произвольной структурой и сохраняет как есть в формате JSON.

Структура класса Dsl:

class Dsl {
    var query: String
    var lang: String
    var fields: Array<String>
    var page: Int
    var size: Int
    var sort: String
}

3) Подходы разработки фронтенда:

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

Описанный подход максимально ускоряет процесс разработки особенно в условиях постоянных модификаций структуры данных, визуальных форм и интерфейсов. А с учётом перспективы добавления в систему сотен различных реестров с уникальной логикой обработки данных, то данный подход является наиболее естественным при работе с большим объёмом неструктурированных данных.

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

Одним из способов ускорения реализации СЭД – в создании отдельной библиотеки фронтенда со всеми оперируемыми моделями данных, где внутри, библиотека также содержит конфигурационный файл Maven для конвертации моделей Angular в схемы Apache Avro с автоматической генерацией Java-классов на нужды сервисов. Сгенерированные классы можно с ходу использовать в DslRepository, где единственное правило – чтобы присутствовало поле с именем id.

Реализация бизнес-логики обычно производится в отдельном сервисе, но типичные алгоритмы всё-таки желательно хранить в виде Kotlin-скриптов в БД, что делает возможным редактировать их в одном из бесплатных визуальных редакторов исходного кода.

Компилировать скрипты можно на этапе старта сервиса либо хранить байткод в кластерном кэше уровня сервиса Embedded Hazelcast – это позволяет оперативно дорабатывать на фронте алгоритмы без перезагрузки подов сервиса. Такой подход также делает бизнес-процессы Activiti полностью динамичными.

4) Консолидация данных:

С десяток форматов документов различных версий могут быть загружены в систему, по которым необходимо организовать полнотекстовый поиск. Бесплатным инструментом извлечения текста с должным качеством является Apache Tika, который также умеет распознавать текст с рисунков через встроенную библиотеку Tesseract OCR.

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

Наиболее эффективный запрос релевантного полнотекстового поиска:

SELECT id, jtree->>'name' as title, 
ts_rank_cd(s.tsv, websearch_to_tsquery('russian', 'рынок(союз|-покрытие)'))
FROM (
      SELECT id, jtree, tsv
      FROM jobject, 
           websearch_to_tsquery('russian', 'рынок(союз|-покрытие)') AS q
      WHERE (tsv @@ q)
) AS s
ORDER BY ts_rank_cd(s.tsv, 
           websearch_to_tsquery('russian', 'рынок(союз|-покрытие)')) DESC;

5) Разработка Data Mining и OLAP

Возможность непосредственно в базе данных запускать алгоритмы машинного обучения и построения OLAP-кубов позволяет многократно уменьшить используемые ресурсы, обеспечивая обработку и вывод результатов в реальном режиме времени.

Встроенный в PostgreSQL в виде расширения инструмент Apache MadLib очень прост в использовании из-за применения запросов в виде SQL, что в равной степени касается и синтаксиса построения OLAP-кубов.

Многие данные в период начального сбора или из открытых источников фрагментированы, либо имеют искажения и работа по восстановлению занимает немало времени. Для этого необходимо применение мощных инструментов, какими обладает PostgreSQL и интегрированный воедино MadLib.

MadLib содержит реализацию всех популярных алгоритмов: SVM (метод опорных векторов), K-Means (классификация методом k-средних) и т.д. В научных институтах СНГ наиболее часто используется рекурсивная линейная регрессия для задач прогнозирования и выявления зависимостей.

В MadLib представлена обычная линейная регрессия, но доработать её до рекурсивной средствами PgSQL не очень сложно. Единственно, можно ещё доработать и сам MadLib (написанный на Python) строящий запросы в реляционной модели, т.е. если внедрить свойства No-SQL, то получаем ускорение в сотни раз.

Есть непрофильные алгоритмы для MadLib, например, латентно-семантический анализ (LSA). Свободных библиотек LSA много, можно внедрить одну из них в виде расширения PostgreSQL. Затем, комбинируя результат релевантного полнотекстового поиска и обученной модели LSA дополнить результат поиска сравнительными коэффициентами соответствия и ссылками на ближайшие документы по смыслу.

6) Динамическая отчётность

Jasper Reports позволяет строить отчёты и графики без взаимодействия с БД, т.е. отправляя на вход Json со всеми данными – сразу формируется отчёт или график в нужном формате. Данной технологии много лет и имеет вокруг сообщество с большим числом разработчиков. Есть множество открытой информации по каждому аспекту использования Jasper Reports. В редакторах Jaspersoft Studio или iReport Designer, помимо прочего, можно строить отчёты с любым уровнем вложенности и разгруппировывать данные по полю без вызова SQL.

Можно сформировать не только сложный отчёт, но и полноценный документ в формате Word или PDF типа договора или коммерческого предложения. Другими словами, использовать Jasper Reports в качестве генератора сложных документов с разнообразием отчёта.

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

В визуальном web-редакторе wysiwyg, схожим с Word (которых великое множество), редактируем шаблон отчёта с расстановкой наименований полей модели и синтаксиса отображения табличных данных из коллекций. Мышкой добавляем из заготовленной заранее коллекции графики и табличные данные. Затем, шаблон в формате HTML или JRXML, как удобнее, сохраняется в БД. Заполненные документы могут выгрузится в любом формате офисного документа.

Jasper Reports прекрасно маппит модели в шаблоны любой сложности, но форматирование выходных Word или PDF иногда бывает скошенным, поэтому конвертируем шаблоны в красивые HTML. А затем, с помощью библиотеки JodConverter на базе LibreOffice сконвертировать полученные HTML в Word или PDF.

Резюме

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

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

Рассматривая полученную платформу СЭД, даже при условии постоянной адаптации к новым типам данных и бизнес-процессов – не требуется программисты, т.к. весь функционал управляется визуальными средствами. Что в итоге, соответствует современным тенденциям, когда понятия «технической поддержки и сопровождения» уходят в рудимент за ненадобностью.

Эффективность бизнеса прямо зависит от оперативности принятия решений на поведение рынка или можно сказать от скорости адаптации под меняющиеся статистические факторы.

UPDATED: На GitHub опубликован демо-проект, показывающий некоторые описанные здесь возможности. Вышло продолжение статьи: Reactive Spring ABAC Security: безопасность уровня Enterprise

Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+10
Комментарии2

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань