Я расскажу о технологической платформе, пригодной для создания информационного ядра системы или приложения. Платформа содержит простой высокоуровневый конструктор модели данных и базовый интерфейс для работы с ней, поддерживает ролевую модель доступа, эмулятор запросов SQL (CRUD), API, а также дает возможность загружать произвольные рабочие места — элементы UI — и наполнять их данными.
У платформы есть некоторые принципиальные отличия от бесконечного множества «конструкторов», из-за чего она и появилась. Некоторые из отличий достойны качественного холивара, другие просто упрощают жизнь разработчика, кем бы он ни был. Несколько приложений уже работают у живых клиентов, из них будут приведены рабочие примеры выполнения задач.
Здесь вы можете собрать веб-приложение, не изучая язык программирования: мы оперируем только бизнес-терминами и формулами, не сложнее, чем в MS Excel. Безусловно, понимание принципов работы баз данных поможет вам разработать более живучий, масштабный и богатый функционалом продукт, но этот сервис не требует специфических знаний для простых решений, которые составляют, навскидку, не меньше 80% прикладной разработки (например, кустарной и всего, что сейчас работает в Экселе).
Сейчас это всего лишь простенький сервис, концепт, реализующий подход к хранению данных и разработке приложений, адептами которого является немногочисленный коллектив одноименного проекта. Сервис не претендует на полноту возможностей, у нас впереди еще много работы, а пока мы только подтвердили для себя концепцию оригинального конструктора приложений, создаем продукт и готовы его развивать в сотрудничестве со всеми желающими.
Интеграл — это архитектурообразующее ядро, дающее базовый пользовательский интерфейс для задания модели данных (метаданные) и работы с данными. Базовый интерфейс использует несколько простейших команд для управления структурой данных (DDL) и 6 команд для изменения самих данных (DML). Эти команды скрыты от пользователя — он видит только поля ввода, таблицы и кнопки. Вы можете, не зная никаких команд, использовать базовый интерфейс для работы с вашими данными и быстро создать информационную систему.
Общая схема взаимодействия сервиса с внешним миром выглядит так:
Ядро делает всю грязную работу: связывание данных, простейшую валидацию, контроль целостности, построение запросов на языке базы данных, приведение типов и прочие неинтересные вам мелочи. Вы только указываете, какие данные необходимо передать или запросить, какой запрос запустить, как будет отформатирован результат в пользовательском интерфейсе. Разумеется, при желании вы можете вмешаться и скорректировать работу ядра в той части, которая касается бизнес-логики.
«Программирование» Интеграла заключается в следующем:
Пользователь заходит в систему, получает меню согласно своей роли, просматривает информацию, вводит данные, запускает запросы, двигая процесс по заданным этапам, получает отчеты. Всё взаимодействие с системой происходит через формы, заданные шаблонами. Очень похоже на работу CMS и, как говорит наша дизайнер, «сделано как в первой Битре».
Для работы используется базовый интерфейс, который можно дополнять и расширять под свои нужды. Формы базового интерфейса доступны на Гитхабе, можно их свободно использовать — переписать полностью с блэкджеком и дамочками или просто «натянуть» нужные стили.
Средства построения модели данных и работы с ней — основное преимущество подхода Интеграл. Здесь мы можем быстро создать схему объектов нашего бизнеса, способную при необходимости так же быстро перестроиться.
Изначально мы получаем базу данных, в которой описаны некоторые сущности (называемые далее объектами или типами). Об их природе и принципах работы ядро Интеграла уже знает. Их немного:
Перечисленные объекты имеют несколько свойств, о которых ядро также знает. В совокупности начальное наполнение дает минимум функционала, который неизбежно присутствует в любом приложении и который позволяет собрать всё остальное.
Структура создается в Редакторе типов, где всё это выглядит примерно так:
Перед вами иерархия объектов: Пользователю назначена Роль, в которую входят несколько Объектов, каждому из которых назначен Доступ. Также есть объект Запрос — это аналог SQL-запросов в базах данных, он содержит Поля, к которым можно применять Функции, Форматы, подсчитывать итоговые значения по ним. Здесь для наглядности связи изображены стрелками между объектами, однако в Редакторе типов объекты-прямоугольники не связаны стрелками, а иерархия обозначается только расположением детей правее от родителя с распространением списка вниз. Так сделано потому, что на небольшой схеме подчиненность объектов и так очевидна, в то время как на большой схеме стрелки не добавляют удобства, а лишь загромождают картину.
Также для простоты восприятия в иерархии отображаются только ссылочные и табличные реквизиты, а простые, такие как Email или Телефон, располагаются списком (по алфавиту) внизу Редактора типов.
Помимо перечисленных выше объектов, Интеграл — это чистый лист, на котором вы можете набросать нужную вам структуру данных с их связями. Любую структуру, какую можно реализовать в реляционной базе данных. Как вы, вероятно, уже догадались, Интеграл сам собран средствами Интеграла.
Для добавления своих объектов — терминов вашего бизнеса — вы просто задаете имя и указываете базовый тип: строка, число, файл и т.д. Всего доступно 16 базовых типов, большинство из которых определяют представление данных, остальные задают поведение — например, контекстное действие.
Чтобы облегчить жизнь пользователю, мы по возможности максимально упростили базовые типы данных. Сами данные любого типа хранятся одинаково — как последовательность байтов, а базовый тип определяет формат вывода, правила сравнения и валидации и прочее, что скрыто от пользователя. Например, пользователю не нужно задумываться заранее, сколько у него будет знаков после десятичной точки в каждом реквизите. Можно вводить числа с любым количество знаков, и в подавляющем большинстве случаев (10-12 знаков до или после запятой) это будет корректно работать без лишних вопросов (по умолчанию для SIGNED выводится 2 знака после запятой). При необходимости вывода чисел в нужном формате, можно привязать эти форматы в виде дополнительных реквизитов и использовать их в созданных пользователем шаблонах, но об этом позже.
Типы названы соответствующими английскими словами, чтобы программистам было привычнее начать с ними работать. При желании можно перевести их на человеческий язык, подправив один справочник в Редакторе типов. Пока мы не наблюдали трудностей с пониманием значений типов ни у какой категории пользователей Интеграла, и есть подозрение, что они как раз появятся, если мы изобретем русскоязычные заменители. Разумеется, обучая пользователя MS Excel работе с Интегралом, мы не используем понятия сущность, реквизит и тому подобные, а оперируем более простыми понятиями, такими как таблица, поле, колонка и т.д.
Допустим, мы работаем с Клиентами, у которых есть имя, телефон, email и адрес. Все эти новые для Интеграла термины, которые также можно назвать объектами, типами или сущностями, добавляются с помощью такой простейшей формы:
Так в Интеграле задаются все термины бизнеса. Затем из них собираются все сущности, с которыми бизнес имеет дело, и они образуют структуру данных. Некоторые термины, такие как телефон, email, адрес уже есть в Интеграле, и их можно использовать в своих целях.
Кликнув имя любого типа, можно добавить ему реквизиты:
В нашем распоряжении весь набор типов, что мы уже добавили в систему. Разумеется, мы можем добавлять Реквизиты, создавая их по мере необходимости, удалять, менять местами, задавать для них простейшие правила: обязательный реквизит, вычисляемое значение, псевдоним, значение по умолчанию. Один и тот же тип может быть добавлен в виде реквизита разным объектам.
Все созданные в редакторе типов объекты немедленно доступны для использования: можно создавать экземпляры этих типов, заполнять их реквизиты. Объекты находятся в Словаре в виде списка всех независимых сущностей, то есть тех типов, которые не являются ни чьими реквизитами. Так мы видим в списке тип Доступ, потому что одноименный реквизит Доступ у типа Объект является ссылкой на независимый справочник Доступов. А вот типы Объекты и Меню отсутствуют в списке, так как напрямую подчинены типу Роль:
Экземпляры объектов Словаря доступны в виде таблиц, к которым можно применять фильтры для поиска нужных записей (по любому реквизиту), списочное удаление, экспорт и импорт. Кликнув «Пользователь» в Словаре, можно увидеть список всех экземпляров типа Пользователь:
Для редактирования записи в таблице нужно нажать на значение в первой колонке этой таблицы, в данном случае первое поле — «Пользователь».
Примечание: Возможность удаления, редактирования, экспорта, видимость реквизитов и другие ограничения в работе с объектами задаются в Роли пользователя с точностью до отдельных реквизитов. Также можно применить маски к значениям объектов и их реквизитов для ограничения доступа по значению.
Для добавления новой записи сначала указывается значение экземпляра этого типа:
Затем заполняются реквизиты этого типа:
К реквизитам применяются простейшие правила, заданные в редакторе типов: email и Имя — обязательные для заполнения поля, Дата — автоматически вычисленное значение текущей даты.
В Редакторе типов есть пара приемов, позволяющих создавать связи между сущностями:
В этом случае всё просто и привычно: можно создать тип Категория, а затем, кликнув на него, выбрать опцию «Создать ссылку»:
В редакторе типов получится два типа с именем Категория: сам тип и ссылка на его экземпляр.
В списке реквизитов их также будет две, ссылка отмечена префиксом в виде стрелки:
Добавив ссылочный реквизит, мы получим две связанные таблицы, и Интеграл выстроит их в редакторе типов согласно иерархии (ссылочные и табличные реквизиты располагаются правее своего родителя):
Ссылочному реквизиту можно назначить псевдоним (выделен на картинке жирным), что будет удобно при использовании одного справочника в различных реквизитах с разными смыслами, например, юр. лицо может быть заказчиком, подрядчиком, грузоотправителем и так далее.
Мы можем использовать в качестве реквизита любой другой зарегистрированный тип, указывая ID его экземпляра в виде числового реквизита и подставляя этот ID в ссылку на форме редактирования или в отчете. Например, можно сделать так, что объектом охраны будут люди, автомобили, здания или авторские права.
Этот вид реквизита удобен для быстрой навигации между связанными данными: можно подчинить объекту массив записей, который всегда будет под рукой при работе с объектом-родителем. Например, сущность Роль содержит два реквизита: списки Объектов и пунктов Меню, каждый из которых имеет реквизиты, поэтому позволяет задать множество значений соответствующего реквизита типу Роль:
В словаре данных это будет выглядеть как набор таблиц, между которыми можно быстро перемещаться, только вместо одной связанной записи будет отображен массив. При переходе от Роли к объектам мы увидим все объекты, включенные в эту роль.
Описанные выше способы создания типов, добавления реквизитов и двух видов связей уже позволяют создать любую структуру данных, возможную в реляционной базе данных.
Для работы с данными в Интеграле существует конструктор запросов. В простейшем случае, который покрывает много больше половины потребностей пользователей, достаточно просто перечислить нужные поля данных, а Интеграл сам объединит в запросе соответствующие таблицы, с учетом связей, заданных в редакторе типов.
Например, выборка всех доступов всех пользователей сервиса согласно их ролям будет сводиться к простому перечислению полей данных. В меню Словарь выбираем объект Запрос:
Задаем имя нашему запросу и нажимаем Добавить:
Заходим в реквизит Поле данных созданного запроса:
Видим пустую таблицу полей, в которую можно добавить объекты из списка. Список сравнительно велик, потому что включает все объекты, зарегистрированные в нашем экземпляре сервиса. Нам нужен Пользователь — выбираем, добавляем.
У созданного поля данных Пользователь есть множество реквизитов, которые нам пока не нужны (мы их обязательно рассмотрим позже в этой статье). Сохраняем запись как есть:
Примечание: конструктор запросов может быть облачен в более привлекательный визуальный интерфейс, компактный и без лишних деталей, как сделано в примере про составные типы. Мы сознательно не делали это в базовом интерфейсе, чтобы не ущемлять его функциональные возможности.
В таблице полей данных появилась строка Пользователь, а список доступных объектов сократился — теперь в нем есть только объекты, связанные с Пользователем. Выбираем Роль, которая нам нужна из постановки задачи:
Теперь в списке появились Объекты и Меню, связанные с Пользователем через Роль. Добавляем Объекты:
Наконец, добавляем описание уровня доступа, которое является реквизитом Доступа:
Всё, наш запрос готов, он включает четыре поля данных из четырех разных таблиц.
Кликнув имя запроса, мы перейдем из списка полей на форму самого запроса, где есть кнопка «Сформировать». Эта кнопка — реквизит, запускающий запрограммированное контекстное действие, в данном случае — вызов формы отчета с указанием ID этого отчета (запроса).
Запущенный запрос вернет отчет обо всех пользователях, ролях, объектах и уровнях доступа:
Мы не задумывались о связях между таблицами объектов и сделали этот отчет меньше чем за минуту. Это достаточно простой отчет, но он отражает возможности конструктора запросов. Отчет можно отфильтровать по значениям полей, а также выгрузить его в Excel.
Любые созданные в сервисе объекты могут быть использованы в выборке данных, причем Интеграл сам свяжет все задействованные таблицы. Конструктор запросов позволяет создавать аналоги практически любых запросов SQL (пока кроме экзотики, типа рекурсивных запросов), при этом он снимает с пользователя заботу про индексы, внешние ключи и другие детали, не относящиеся непосредственно к бизнесу пользователя.
В некоторых случаях вам может понадобиться указать свои правила объединения таблиц, как вы привыкли это делать при написании SQL-запросов, и Интеграл позволит вам это сделать. Об этом я расскажу далее в этой статье.
Помимо базового интерфейса пользователь может создавать произвольные формы в виде шаблонов с точками вставки, в которые Интеграл подставит данные. Данные могут быть выбраны запросом из базы или взяты из контекста работы с шаблоном: имя и ID пользователя; параметры, переданные через GET и POST; данные вышестоящих и соседних блоков; глобальные константы и т.д.
Обработка HTML-контента на основе шаблонов производится на сервере, и каждое действие пользователя приводит к формированию новой страницы. Изначально в интерфейсе всего 6 шаблонов форм — по одному на каждый режим работы плюс одна главная страница.
Формы имеют следующее назначение:
Пользовательские формы могут быть загружены в меню Файлы, в директорию templates, где изначально находятся главная страница и пустая начальная форма (содержащая приветствие и ссылку на Руководство пользователя Интеграл):
Если кому понадобится заменить остальные формы базового интерфейса, то можно скачать их с Гитхаба и подложить в эту же папку.
На момент написания этой статьи точки вставки, используемые в шаблонах, бывают трёх видов:
«Разметка блоков» определяет фрагменты шаблона, повторяющиеся ноль и более раз, в зависимости от того, сколько записей предоставит источник данных для этого блока — имя источника указано в разметке. «Поля данных» указывают место вставки конкретных значений из записей источника данных. «Файл» содержит текст шаблона, который будет найден в файловой системе, разобран и вставлен в указанное место. Уровень вложенности блоков и файлов не ограничивается системой.
На рисунке ниже приведен HTML-код шаблона страницы базового интерфейса (main.html — главная страница с меню), где зеленым цветом выделены границы блоков TopMenu и File. Когда парсер будет обрабатывать этот шаблон, он обратится к базе за запросом TopMenu и получит набор записей, в которых он попытается отыскать поля данных TOP_MENU_HREF и TOP_MENU. Результат выполнения запроса — отчет — обведен на рисунке серой рамкой. Значения этих полей будут подставлены в шаблон вместо имен этих полей в фигурных скобках. Фрагмент шаблона, выделенный серым на рисунке, будет повторен столько раз, сколько записей вернет набор TopMenu. Сами теги <!— Begin:… —> и <!— End:… —> не будут включены в результат.
Далее парсер встретит конструкцию <!— File: a —>, вместо которой будет вставлено содержимое файла шаблона с именем, определенным параметром a, предварительно также обработанного парсером. В результате на странице будет отображено меню, состоящее из доступных роли пользователя пунктов, и рабочее место согласно выбранному ранее пункту меню. Кликнув нужный объект, пользователь перейдет к другому рабочему месту, точно так же собранному парсером, но с другой формой в блоке File:
Таким образом сервис позволяет построить любую структуру интерфейса приложения, применить логику, заложенную в запросах, для выборки данных, наполнить формы этими данными и вносить изменения в базу средствами базового интерфейса или кастомизированными пользовательскими формами.
Результаты запросов доступны в виде JSON, поэтому вы можете сделать свое одностраничное приложение, которое будет использовать свой шаблонизатор, опираясь на возможности Интеграла для организации базы данных и механизма ролей, конструирования запросов. Для запроса отчета в формате JSON необходимо передать параметр JSON при вызове отчета:
Редактор типов, построитель отчетов и пользовательские формы дают такую комбинацию возможностей, которую вы вряд ли найдете в другом средстве разработки. Блоки данных и правила их заполнения дополняют интеграл до законченной платформы: с помощью всех этих инструментов вы можете собрать приложение, ничем не ограниченное по функционалу и сложности модели данных.
При желании можно создать Single-Page Application, используя Интеграл и один из современных реактивных UI js фрэймворков, например Vue или React.
Возможно, читатель уже немного заскучал без полиморфизма, наследования и инкапсуляции… Не уходите, в следующих разделах вас ждет немного хардкора.
После краткого обзора концептуальных возможностей Интеграла далее я расскажу про некоторые самые интересные, на мой взгляд, особенности этого сервиса. Если кому-то захочется знать больше, то для вас есть Руководство разработчика на 40 листах, где описано почти всё, что известно про сервис. Кому, наоборот, уже достаточно информации, можно перейти к заключению.
Напомню, что пока сервис существует в достаточно примитивном виде, а некоторые моменты выглядят откровенно наивно. Мы видим это и работаем над этим. Наш Change request log сейчас содержит работ на 500+ часов для доведения его до сервиса приличного уровня.
В Интеграле проиндексированы все данные всех таблиц. Да, вы можете сделать таблицу с любым количеством колонок, загрузить туда много-много записей, а затем фильтровать данные по произвольным полям, и поиск будет производиться с использованием индекса, если он применим к условию выборки. У вас нет необходимости предварительно заботиться о том, какие индексы могут понадобиться в будущем, более того, Интеграл не предоставляет выбора: индексировано всё.
Конструктор запросов также выберет подходящие индексы для фильтрования и объединения данных в отчете, по-прежнему освобождая вас от дум о связях, ключах и индексах везде, где это возможно. В результате запросы выполняются примерно так, как они бы выполнялись в реляционной базе данных с настроенными вручную индексами.
Такое индексирование требует больше места для хранения данных, чем выборочно индексированная база (в 2-3 раза), однако экономит силы и нервы при разработке и поддержке, что в деньгах получается несравнимо дешевле. Эти накладные расходы увеличиваются линейно, а в ряде случаев получаются меньше, чем расходы на хранение данных в базе с множеством составных индексов, в платформе 1С с её GUID и денормализацией или, тем более, в CMS вроде Битрикс с его инфоблоками.
Чтобы не разводить теоретический холивар про индексы и производительность, мы развернули тестовый стенд, где можно посмотреть и потрогать, как это работает на нескольких миллионах записей. Этот стенд подтормаживает иногда, как и любая база с миллионами записей и десятками одновременных запросов, поэтому адекватное представление о производительности там получить можно.
Интеграл обеспечивает минимальные средства поддержания целостности данных, например, не позволяя удалить справочное значение, если оно используется в качестве реквизита. То же самое относится к типам и реквизитам объектов — нельзя удалить тип или реквизит, если имеются его экземпляры или он упоминается в запросе или роли. Также есть проверка на уникальность добавляемых объектов, но только в самом простом случае: если добавляется неуникальное значение объекта, отмеченного уникальным в Редакторе типов.
Из ядра Интеграла намеренно вынесены такие вещи как триггеры, ключи, constraint’ы и многое другое. Также было скрыто всё, что было возможно скрыть от пользователя, например, системные ID записей (это бывает непривычно для программиста, но достаточно быстро усваивается). Тем не менее, при желании любой ID можно достать из системы, а в ряде случаев это просто необходимо, для общения по API, например.
Следует заметить, что Интеграл развивается не в сторону усиления технических барьерных средств, вроде механизма транзакций, блокировок и ограничений, а больше поощряет составление бизнес-процесса таким образом, чтобы избежать коллизии в принципе, имея прозрачную картину происходящего. Образно, чем бить по рукам в случае ошибки, лучше просто не дать пользователю ошибаться. Эта скользкая тема будет обсуждена в отдельной статье, подкрепленной более конкретной теорией и примерами из жизни.
Интеграл умеет вносить изменения в данные, выбранные в результате запроса. Его архитектура позволяет так делать, поэтому грех не воспользоваться возможностью. По умолчанию изменения производятся в два этапа: предварительный просмотр предстоящих изменений, затем подтверждение. Также можно выполнить запрос без подтверждения.
В конструкторе отчетов у Поля данных есть реквизит «Присвоить». В этот реквизит можно записать какое-либо значение, будь то константа или выражение, вычисляемое из значений других полей этого же запроса. Это выражение будет вычислено для каждой записи, возвращенной запросом, и присвоено тому полю данных, для которого оно указано.
Запрос на изменение может создавать, изменять и удалять данные. Это бывает удобно, например, если вам необходимо запланировать встречи с клиентами, провести биллинговые операции или просто удалить старые записи. Посмотрим, как это работает на примере небольшой структуры данных:
Для начала проставим примечание «Осень 2017» для всех клиентов, с кем были заключены договоры осенью 2017 года. Для этого составим запрос, отбирающий таких клиентов, и выглядеть он будет примерно так:
Запрос отбирает нужные данные, применяя к дате договора фильтр с 1 сентября по 30 ноября 2017 года. В результате получится следующий отчет:
Теперь для этих клиентов зададим новое значение реквизита Примечание (реквизит «Присвоить»), а также дадим именам колонок более симпатичные заголовки:
Запустив наш обновленный отчет, получим подсказку, какие изменения будут внесены в данные этим запросом:
В появившейся колонке «Выполнить» мы видим, как будут изменены данные, и, похоже, мы можем потерять старое значение примечания к клиенту Дмитрий М. Чтобы не допустить этого, изменим реквизит «Присвоить», применив функцию языка SQL, которая сцепит существующий текст с новым. Мы присвоим псевдоним MEMO этой колонке, и по этому псевдониму сможем подставить ее значение в функцию CONCAT(), сцепляющую фрагменты текста:
Теперь запрос сделает точно то, что мы хотим:
По нажатию «Выполнить» запрос внесет изменения в базу данных, выдав соответствующий отчет:
В Словаре, в таблице клиентов мы увидим новые данные по клиентам:
У вас может возникнуть вопрос: что будет, если какой-то объект встречается в отчете больше одного раза, и мы пытаемся его изменить. Ответ: объект будет изменен только 1 раз. Например, в следующем отчете мы добавляем Примечание клиента, который упомянут в нескольких строках отчета, и Интеграл выполнит изменение только 1 раз для каждого клиента:
Это достаточно простой пример использования запросов на изменение данных. На практике может понадобиться обновить несколько реквизитов в различных таблицах, например, обфусцировать (обезличить) персональные данные клиентов перед выгрузкой данных на тестовый сервер.
Сервис позволяет указать новые значения для нужного количества полей данных, в том числе из разных сущностей, — Интеграл аккуратно разнесет все эти изменения по нужным таблицам.
Теперь продемонстрируем создание новых записей. Пример: нам нужно запланировать на полдень 1 февраля 2018 года встречу со всеми клиентами в статусе «Клиент». Создадим такой запрос:
Получившийся из него отчет выведет подсказку о создаваемых записях, а по нажатию «Выполнить» будут добавлены записи о встречах, при этом повторный запуск запроса не приведет к дублированию уже созданных записей, если таковые имеются.
В Словаре во Встречах Клиентов появятся эти записи:
Для удаления записей достаточно присвоить им пустые значения — "". Пример удаления записей по встречам весьма прост:
Интеграл не тратит место на пустые значения, поэтому записи будут просто удалены, со всеми своими реквизитами и подчиненными таблицами, если таковые есть:
Интеграл позволяет обратиться к внешним системам (сайты, API, файлы и т.д.) по протоколу http(s) во время выполнения запроса. Для этого необходимо задать шаблон URL внешнего источника в поле URL отчета (в этом примере я переименовал тип Запрос в Отчет в Редакторе типов). В шаблоне можно использовать значения полей отчета, заключив их имена в квадратные скобки.
Далее при выборе колонок отчета можно задать им имена, и значения этих колонок будут подставлены в шаблон URL:
Функция abn_URL заменит содержимое своего поля на результат выполнения запроса к полученному URL, вычисленному для каждой строки отчета. В результате выполнения, например, заданного выше запроса к API поставщика SMS-сервиса мы получим ответ сервиса с идентификатором отправленного сообщения, которое придет на заданный нами телефон [phone] получателю с именем [name]:
Результат запроса к URL можно также сохранить в базе, присвоив его значение какому-либо полю.
Напоследок хотелось бы привести пример, который почему-то сильно впечатлил нашего клиента из среды 1С — задача про составные типы. Задача была сделать отчет об операциях в разрезе разных аналитик, состав которых зависел от набора счетов в операции. Для этого отчета пришлось написать немного javascript в пользовательском интерфейсе для простоты восприятия, формы со всем этим безобразием (сверстано быстро и грязно, простите) доступны на Гитхабе: Aggregate Types.
Итак, дан план счетов с указанием набора аналитик для каждого счета (на рисунке — фрагмент плана):
План счетов был импортирован в Интеграл, а объекты аналитик — типы, имеющие набор реквизитов, были созданы в Редакторе типов, например:
Номенклатура — иерархический справочник со ссылкой на родительский элемент.
Далее в Редакторе типов мы создали тип Операция со счетами Дт и Кт, для которых в свою очередь задан допустимый набор объектов аналитики:
Незаполненные реквизиты объекта не занимают место в базе данных. Если набор объектов аналитики очень большой, то можно не загромождать ими список реквизитов, а создавать эти типы с определенным реквизитом, допускающим добавление в состав сложного типа. Хотя можно даже и без этого обойтись и добавлять всё, к чему у пользователя есть доступ.
При заведении операций можно задать аналитику согласно вовлеченным счетам и её фактическим параметрам:
Далее мы формируем отчет по операциям, где можно выбрать произвольный набор интересующих нас аналитик отдельно для счетов Дт и Кт. По каждой аналитике можно задать условия отбора записей (поле Фильтр):
Мы не хотим заставлять конечного пользователя работать с базовым интерфейсом Интеграла даже в прототипе его приложения, поэтому сверстали для него такую форму ввода. На самом деле во время задания аналитик добавляются и удаляются записи в Поля данных Запроса «Отчет по аналитикам», и вот как это же выглядит в Словаре:
Этот отчет выберет все операции, которые имеют требуемый набор аналитик, удовлетворяющий условиям отбора. Конечно, мы можем включить в отчет реквизиты объектов аналитики на всю их глубину, когда заказчику это понадобится.
Результат отчета — это сумма операций для каждой комбинации аналитик:
Основная задача Интеграла для широкого пользователя: дать ему возможность ворочать своими данными напрямую, не применяя или даже не зная языков программирования и теории баз данных. Так в свое время появился язык SQL, который предлагал любому бухгалтеру самостоятельно работать с данными на человеческом языке. Мы идем тем же путем, но стараемся обойтись и вовсе без языка и теорий, поскольку любая необходимость делать нечто, не касающееся бизнеса, отталкивает человека от использования инструмента автоматизации.
Девиз Интеграла: «не сложнее, чем в Excel». Интеграл может заменить Excel более гибким и защищенным средством хранения и обработки данных. Кто-то переживает, что это SaaS? Спросите его, не пересылает ли он по почте свои эксели и не хранит ли их в гуглодоксах, то есть сервисах, которые профессионально умеют шерстить данные, выуживая из них всю нужную информацию для себя и уполномоченных служб?
В первую очередь это сервис небольшого бизнеса, в котором зачастую владельцу приходится самому вести разработку или как минимум управлять ею. IT-составляющая критически важна для компании, поскольку именно она определяет маневренность и гибкость бизнеса. Мы стараемся дать возможность начать работу сразу, не отвлекаться на непрофильные темы и смочь всё в корне переделать при необходимости. Поменялся принцип учета? Выверни всю модель наизнанку, выкинь лишнее и добавь нужное! Интеграл позволяет сделать это быстро и достаточно наглядно.
Программисты всегда мечтали меньше программировать банальные вещи, чтобы было больше времени для великих свершений.
Наш проект — попытка сделать инструмент для этого, и команде Интеграла удалось добиться его практической работоспособности на тех объемах данных, которые могут встретиться в прикладной разработке нижнего и среднего ценового сегмента. Под «практической работоспособностью» имеется в виду такие критерии:
Ядро Интеграла достаточно компактно, а низкоуровневая работа с данными по нашим расчетам может быть вообще выполнена в железе. Прощайте, Meltdown и Spectre; здравствуй, импортозамещение… Неожиданно? А между тем в IT уже давно не происходит ничего принципиально нового (да простят меня апологеты квантовых компьютеров, но до практического применения им еще далеко), и плох тот мечтатель, который не стремится сделать что-то эдакое на практике. Архитектура сервиса в корне отличается от существующих платформ, и, мы верим, может изменить мир IT, как изменило его, например, появление открытой архитектуры IBM PC.
Сервис активно развивается только последние пару лет, и мы видим, что при таком небольшом (и нестабильном, ведь всем нужны деньги) коллективе нам до нормального продукта работать еще декаду, а команде уже приходится жертвовать многим для продолжения проекта. При этом в самом направлении мы видим несомненные перспективы, что, в частности, побудило написать эту статью — мы хотим сотрудничать со всеми, кто готов развивать, улучшать и популяризировать подход Интеграла.
Руководство разработчика
Регистрация своего ознакомительного экземпляра базы на сервисе: https://tryint.ru
У платформы есть некоторые принципиальные отличия от бесконечного множества «конструкторов», из-за чего она и появилась. Некоторые из отличий достойны качественного холивара, другие просто упрощают жизнь разработчика, кем бы он ни был. Несколько приложений уже работают у живых клиентов, из них будут приведены рабочие примеры выполнения задач.
Здесь вы можете собрать веб-приложение, не изучая язык программирования: мы оперируем только бизнес-терминами и формулами, не сложнее, чем в MS Excel. Безусловно, понимание принципов работы баз данных поможет вам разработать более живучий, масштабный и богатый функционалом продукт, но этот сервис не требует специфических знаний для простых решений, которые составляют, навскидку, не меньше 80% прикладной разработки (например, кустарной и всего, что сейчас работает в Экселе).
Disclaimer
Сейчас это всего лишь простенький сервис, концепт, реализующий подход к хранению данных и разработке приложений, адептами которого является немногочисленный коллектив одноименного проекта. Сервис не претендует на полноту возможностей, у нас впереди еще много работы, а пока мы только подтвердили для себя концепцию оригинального конструктора приложений, создаем продукт и готовы его развивать в сотрудничестве со всеми желающими.
Интеграл
Интеграл — это архитектурообразующее ядро, дающее базовый пользовательский интерфейс для задания модели данных (метаданные) и работы с данными. Базовый интерфейс использует несколько простейших команд для управления структурой данных (DDL) и 6 команд для изменения самих данных (DML). Эти команды скрыты от пользователя — он видит только поля ввода, таблицы и кнопки. Вы можете, не зная никаких команд, использовать базовый интерфейс для работы с вашими данными и быстро создать информационную систему.
Общая схема взаимодействия сервиса с внешним миром выглядит так:
Ядро делает всю грязную работу: связывание данных, простейшую валидацию, контроль целостности, построение запросов на языке базы данных, приведение типов и прочие неинтересные вам мелочи. Вы только указываете, какие данные необходимо передать или запросить, какой запрос запустить, как будет отформатирован результат в пользовательском интерфейсе. Разумеется, при желании вы можете вмешаться и скорректировать работу ядра в той части, которая касается бизнес-логики.
«Программирование» Интеграла заключается в следующем:
- задание параметров процессов — этапы, роли, свойства, структуры, форматы и т.д.;
- создание запросов на выборку и изменение данных, включая создание и изменение самих запросов;
- создание шаблонов пользовательского интерфейса с точками вставки для данных.
Пользователь заходит в систему, получает меню согласно своей роли, просматривает информацию, вводит данные, запускает запросы, двигая процесс по заданным этапам, получает отчеты. Всё взаимодействие с системой происходит через формы, заданные шаблонами. Очень похоже на работу CMS и, как говорит наша дизайнер, «сделано как в первой Битре».
Для работы используется базовый интерфейс, который можно дополнять и расширять под свои нужды. Формы базового интерфейса доступны на Гитхабе, можно их свободно использовать — переписать полностью с блэкджеком и дамочками или просто «натянуть» нужные стили.
Структура данных
Средства построения модели данных и работы с ней — основное преимущество подхода Интеграл. Здесь мы можем быстро создать схему объектов нашего бизнеса, способную при необходимости так же быстро перестроиться.
Изначально мы получаем базу данных, в которой описаны некоторые сущности (называемые далее объектами или типами). Об их природе и принципах работы ядро Интеграла уже знает. Их немного:
- Пользователь;
- Роль пользователя;
- Объекты, входящие в Роль (все объекты Интеграла, включая Роль и Пользователя);
- Уровень доступа к объекту;
- Запрос (аналог SQL-запроса в реляционной базе данных);
- Поля запроса (имена всех объектов системы, включая вновь создаваемые).
Перечисленные объекты имеют несколько свойств, о которых ядро также знает. В совокупности начальное наполнение дает минимум функционала, который неизбежно присутствует в любом приложении и который позволяет собрать всё остальное.
Структура создается в Редакторе типов, где всё это выглядит примерно так:
Перед вами иерархия объектов: Пользователю назначена Роль, в которую входят несколько Объектов, каждому из которых назначен Доступ. Также есть объект Запрос — это аналог SQL-запросов в базах данных, он содержит Поля, к которым можно применять Функции, Форматы, подсчитывать итоговые значения по ним. Здесь для наглядности связи изображены стрелками между объектами, однако в Редакторе типов объекты-прямоугольники не связаны стрелками, а иерархия обозначается только расположением детей правее от родителя с распространением списка вниз. Так сделано потому, что на небольшой схеме подчиненность объектов и так очевидна, в то время как на большой схеме стрелки не добавляют удобства, а лишь загромождают картину.
Также для простоты восприятия в иерархии отображаются только ссылочные и табличные реквизиты, а простые, такие как Email или Телефон, располагаются списком (по алфавиту) внизу Редактора типов.
Помимо перечисленных выше объектов, Интеграл — это чистый лист, на котором вы можете набросать нужную вам структуру данных с их связями. Любую структуру, какую можно реализовать в реляционной базе данных. Как вы, вероятно, уже догадались, Интеграл сам собран средствами Интеграла.
Для добавления своих объектов — терминов вашего бизнеса — вы просто задаете имя и указываете базовый тип: строка, число, файл и т.д. Всего доступно 16 базовых типов, большинство из которых определяют представление данных, остальные задают поведение — например, контекстное действие.
Тип | Описание |
---|---|
SHORT | Короткая строка (до 127 символов) |
CHARS | Строка без ограничения длины |
DATE | Дата |
NUMBER | Целое число |
SIGNED | Число с десятичной точкой |
BOOLEAN | Логическое значение (Да/Нет) |
MEMO | Многострочный текст (отличается от CHARS только окном ввода) |
DATETIME | Дата с указанием времени |
FILE | Файл |
HTML | HTML-код (теги не экранируются при выводе в запросах) |
BUTTON | Кнопка для контекстного действия (отчет, запрос, ссылка) |
PWD | Пароль (хэш, посолен именем базы, пользователя и чем-то ещё) |
GRANT | Объект доступа (ссылка на любой объект системы) |
CALCULATABLE | Вычисляемое значение (временно упразднено) |
REPORT_COLUMN | Поле данных (ссылка на объект системы) |
FULLTEXT | Строка с поддержкой полнотекстового поиска (в следующем релизе) |
Чтобы облегчить жизнь пользователю, мы по возможности максимально упростили базовые типы данных. Сами данные любого типа хранятся одинаково — как последовательность байтов, а базовый тип определяет формат вывода, правила сравнения и валидации и прочее, что скрыто от пользователя. Например, пользователю не нужно задумываться заранее, сколько у него будет знаков после десятичной точки в каждом реквизите. Можно вводить числа с любым количество знаков, и в подавляющем большинстве случаев (10-12 знаков до или после запятой) это будет корректно работать без лишних вопросов (по умолчанию для SIGNED выводится 2 знака после запятой). При необходимости вывода чисел в нужном формате, можно привязать эти форматы в виде дополнительных реквизитов и использовать их в созданных пользователем шаблонах, но об этом позже.
Типы названы соответствующими английскими словами, чтобы программистам было привычнее начать с ними работать. При желании можно перевести их на человеческий язык, подправив один справочник в Редакторе типов. Пока мы не наблюдали трудностей с пониманием значений типов ни у какой категории пользователей Интеграла, и есть подозрение, что они как раз появятся, если мы изобретем русскоязычные заменители. Разумеется, обучая пользователя MS Excel работе с Интегралом, мы не используем понятия сущность, реквизит и тому подобные, а оперируем более простыми понятиями, такими как таблица, поле, колонка и т.д.
Допустим, мы работаем с Клиентами, у которых есть имя, телефон, email и адрес. Все эти новые для Интеграла термины, которые также можно назвать объектами, типами или сущностями, добавляются с помощью такой простейшей формы:
Так в Интеграле задаются все термины бизнеса. Затем из них собираются все сущности, с которыми бизнес имеет дело, и они образуют структуру данных. Некоторые термины, такие как телефон, email, адрес уже есть в Интеграле, и их можно использовать в своих целях.
Реквизиты
Кликнув имя любого типа, можно добавить ему реквизиты:
В нашем распоряжении весь набор типов, что мы уже добавили в систему. Разумеется, мы можем добавлять Реквизиты, создавая их по мере необходимости, удалять, менять местами, задавать для них простейшие правила: обязательный реквизит, вычисляемое значение, псевдоним, значение по умолчанию. Один и тот же тип может быть добавлен в виде реквизита разным объектам.
Все созданные в редакторе типов объекты немедленно доступны для использования: можно создавать экземпляры этих типов, заполнять их реквизиты. Объекты находятся в Словаре в виде списка всех независимых сущностей, то есть тех типов, которые не являются ни чьими реквизитами. Так мы видим в списке тип Доступ, потому что одноименный реквизит Доступ у типа Объект является ссылкой на независимый справочник Доступов. А вот типы Объекты и Меню отсутствуют в списке, так как напрямую подчинены типу Роль:
Экземпляры объектов Словаря доступны в виде таблиц, к которым можно применять фильтры для поиска нужных записей (по любому реквизиту), списочное удаление, экспорт и импорт. Кликнув «Пользователь» в Словаре, можно увидеть список всех экземпляров типа Пользователь:
Для редактирования записи в таблице нужно нажать на значение в первой колонке этой таблицы, в данном случае первое поле — «Пользователь».
Примечание: Возможность удаления, редактирования, экспорта, видимость реквизитов и другие ограничения в работе с объектами задаются в Роли пользователя с точностью до отдельных реквизитов. Также можно применить маски к значениям объектов и их реквизитов для ограничения доступа по значению.
Для добавления новой записи сначала указывается значение экземпляра этого типа:
Затем заполняются реквизиты этого типа:
К реквизитам применяются простейшие правила, заданные в редакторе типов: email и Имя — обязательные для заполнения поля, Дата — автоматически вычисленное значение текущей даты.
В Редакторе типов есть пара приемов, позволяющих создавать связи между сущностями:
- Для любого типа можно создать ссылку на этот тип, а затем использовать эту ссылку в качестве реквизита — указателя на экземпляр исходного типа
- Реквизит сущности, сам имеющий реквизиты, доступен для редактирования в виде подчиненной таблицы, в которую можно добавлять упорядоченные записи
Реквизит — ссылка на тип
В этом случае всё просто и привычно: можно создать тип Категория, а затем, кликнув на него, выбрать опцию «Создать ссылку»:
В редакторе типов получится два типа с именем Категория: сам тип и ссылка на его экземпляр.
В списке реквизитов их также будет две, ссылка отмечена префиксом в виде стрелки:
Добавив ссылочный реквизит, мы получим две связанные таблицы, и Интеграл выстроит их в редакторе типов согласно иерархии (ссылочные и табличные реквизиты располагаются правее своего родителя):
Ссылочному реквизиту можно назначить псевдоним (выделен на картинке жирным), что будет удобно при использовании одного справочника в различных реквизитах с разными смыслами, например, юр. лицо может быть заказчиком, подрядчиком, грузоотправителем и так далее.
Мы можем использовать в качестве реквизита любой другой зарегистрированный тип, указывая ID его экземпляра в виде числового реквизита и подставляя этот ID в ссылку на форме редактирования или в отчете. Например, можно сделать так, что объектом охраны будут люди, автомобили, здания или авторские права.
Реквизит — подчиненная таблица
Этот вид реквизита удобен для быстрой навигации между связанными данными: можно подчинить объекту массив записей, который всегда будет под рукой при работе с объектом-родителем. Например, сущность Роль содержит два реквизита: списки Объектов и пунктов Меню, каждый из которых имеет реквизиты, поэтому позволяет задать множество значений соответствующего реквизита типу Роль:
В словаре данных это будет выглядеть как набор таблиц, между которыми можно быстро перемещаться, только вместо одной связанной записи будет отображен массив. При переходе от Роли к объектам мы увидим все объекты, включенные в эту роль.
Описанные выше способы создания типов, добавления реквизитов и двух видов связей уже позволяют создать любую структуру данных, возможную в реляционной базе данных.
Запросы
Для работы с данными в Интеграле существует конструктор запросов. В простейшем случае, который покрывает много больше половины потребностей пользователей, достаточно просто перечислить нужные поля данных, а Интеграл сам объединит в запросе соответствующие таблицы, с учетом связей, заданных в редакторе типов.
Например, выборка всех доступов всех пользователей сервиса согласно их ролям будет сводиться к простому перечислению полей данных. В меню Словарь выбираем объект Запрос:
Задаем имя нашему запросу и нажимаем Добавить:
Заходим в реквизит Поле данных созданного запроса:
Видим пустую таблицу полей, в которую можно добавить объекты из списка. Список сравнительно велик, потому что включает все объекты, зарегистрированные в нашем экземпляре сервиса. Нам нужен Пользователь — выбираем, добавляем.
У созданного поля данных Пользователь есть множество реквизитов, которые нам пока не нужны (мы их обязательно рассмотрим позже в этой статье). Сохраняем запись как есть:
Примечание: конструктор запросов может быть облачен в более привлекательный визуальный интерфейс, компактный и без лишних деталей, как сделано в примере про составные типы. Мы сознательно не делали это в базовом интерфейсе, чтобы не ущемлять его функциональные возможности.
В таблице полей данных появилась строка Пользователь, а список доступных объектов сократился — теперь в нем есть только объекты, связанные с Пользователем. Выбираем Роль, которая нам нужна из постановки задачи:
Теперь в списке появились Объекты и Меню, связанные с Пользователем через Роль. Добавляем Объекты:
Наконец, добавляем описание уровня доступа, которое является реквизитом Доступа:
Всё, наш запрос готов, он включает четыре поля данных из четырех разных таблиц.
Кликнув имя запроса, мы перейдем из списка полей на форму самого запроса, где есть кнопка «Сформировать». Эта кнопка — реквизит, запускающий запрограммированное контекстное действие, в данном случае — вызов формы отчета с указанием ID этого отчета (запроса).
Запущенный запрос вернет отчет обо всех пользователях, ролях, объектах и уровнях доступа:
Мы не задумывались о связях между таблицами объектов и сделали этот отчет меньше чем за минуту. Это достаточно простой отчет, но он отражает возможности конструктора запросов. Отчет можно отфильтровать по значениям полей, а также выгрузить его в Excel.
Любые созданные в сервисе объекты могут быть использованы в выборке данных, причем Интеграл сам свяжет все задействованные таблицы. Конструктор запросов позволяет создавать аналоги практически любых запросов SQL (пока кроме экзотики, типа рекурсивных запросов), при этом он снимает с пользователя заботу про индексы, внешние ключи и другие детали, не относящиеся непосредственно к бизнесу пользователя.
В некоторых случаях вам может понадобиться указать свои правила объединения таблиц, как вы привыкли это делать при написании SQL-запросов, и Интеграл позволит вам это сделать. Об этом я расскажу далее в этой статье.
Шаблоны интерфейса пользователя
Помимо базового интерфейса пользователь может создавать произвольные формы в виде шаблонов с точками вставки, в которые Интеграл подставит данные. Данные могут быть выбраны запросом из базы или взяты из контекста работы с шаблоном: имя и ID пользователя; параметры, переданные через GET и POST; данные вышестоящих и соседних блоков; глобальные константы и т.д.
Обработка HTML-контента на основе шаблонов производится на сервере, и каждое действие пользователя приводит к формированию новой страницы. Изначально в интерфейсе всего 6 шаблонов форм — по одному на каждый режим работы плюс одна главная страница.
Формы имеют следующее назначение:
- Главная страница, как правило, содержащая стили и основное меню (или несколько);
- Редактор типов, отображающий структуру данных;
- Словарь — список всех независимых типов;
- Список экземпляров выбранного типа (списки пользователей, ролей, объектов, рассмотренные выше);
- Форма редактирования экземпляра типа;
- Отчет.
Пользовательские формы могут быть загружены в меню Файлы, в директорию templates, где изначально находятся главная страница и пустая начальная форма (содержащая приветствие и ссылку на Руководство пользователя Интеграл):
Если кому понадобится заменить остальные формы базового интерфейса, то можно скачать их с Гитхаба и подложить в эту же папку.
На момент написания этой статьи точки вставки, используемые в шаблонах, бывают трёх видов:
- разметка блоков шаблона;
- поле данных;
- файл.
«Разметка блоков» определяет фрагменты шаблона, повторяющиеся ноль и более раз, в зависимости от того, сколько записей предоставит источник данных для этого блока — имя источника указано в разметке. «Поля данных» указывают место вставки конкретных значений из записей источника данных. «Файл» содержит текст шаблона, который будет найден в файловой системе, разобран и вставлен в указанное место. Уровень вложенности блоков и файлов не ограничивается системой.
На рисунке ниже приведен HTML-код шаблона страницы базового интерфейса (main.html — главная страница с меню), где зеленым цветом выделены границы блоков TopMenu и File. Когда парсер будет обрабатывать этот шаблон, он обратится к базе за запросом TopMenu и получит набор записей, в которых он попытается отыскать поля данных TOP_MENU_HREF и TOP_MENU. Результат выполнения запроса — отчет — обведен на рисунке серой рамкой. Значения этих полей будут подставлены в шаблон вместо имен этих полей в фигурных скобках. Фрагмент шаблона, выделенный серым на рисунке, будет повторен столько раз, сколько записей вернет набор TopMenu. Сами теги <!— Begin:… —> и <!— End:… —> не будут включены в результат.
Далее парсер встретит конструкцию <!— File: a —>, вместо которой будет вставлено содержимое файла шаблона с именем, определенным параметром a, предварительно также обработанного парсером. В результате на странице будет отображено меню, состоящее из доступных роли пользователя пунктов, и рабочее место согласно выбранному ранее пункту меню. Кликнув нужный объект, пользователь перейдет к другому рабочему месту, точно так же собранному парсером, но с другой формой в блоке File:
Таким образом сервис позволяет построить любую структуру интерфейса приложения, применить логику, заложенную в запросах, для выборки данных, наполнить формы этими данными и вносить изменения в базу средствами базового интерфейса или кастомизированными пользовательскими формами.
Результаты запросов доступны в виде JSON, поэтому вы можете сделать свое одностраничное приложение, которое будет использовать свой шаблонизатор, опираясь на возможности Интеграла для организации базы данных и механизма ролей, конструирования запросов. Для запроса отчета в формате JSON необходимо передать параметр JSON при вызове отчета:
Редактор типов, построитель отчетов и пользовательские формы дают такую комбинацию возможностей, которую вы вряд ли найдете в другом средстве разработки. Блоки данных и правила их заполнения дополняют интеграл до законченной платформы: с помощью всех этих инструментов вы можете собрать приложение, ничем не ограниченное по функционалу и сложности модели данных.
При желании можно создать Single-Page Application, используя Интеграл и один из современных реактивных UI js фрэймворков, например Vue или React.
Возможно, читатель уже немного заскучал без полиморфизма, наследования и инкапсуляции… Не уходите, в следующих разделах вас ждет немного хардкора.
Немного глубже и подробнее о вышеописанном
После краткого обзора концептуальных возможностей Интеграла далее я расскажу про некоторые самые интересные, на мой взгляд, особенности этого сервиса. Если кому-то захочется знать больше, то для вас есть Руководство разработчика на 40 листах, где описано почти всё, что известно про сервис. Кому, наоборот, уже достаточно информации, можно перейти к заключению.
Напомню, что пока сервис существует в достаточно примитивном виде, а некоторые моменты выглядят откровенно наивно. Мы видим это и работаем над этим. Наш Change request log сейчас содержит работ на 500+ часов для доведения его до сервиса приличного уровня.
Индексирование и целостность данных
В Интеграле проиндексированы все данные всех таблиц. Да, вы можете сделать таблицу с любым количеством колонок, загрузить туда много-много записей, а затем фильтровать данные по произвольным полям, и поиск будет производиться с использованием индекса, если он применим к условию выборки. У вас нет необходимости предварительно заботиться о том, какие индексы могут понадобиться в будущем, более того, Интеграл не предоставляет выбора: индексировано всё.
Конструктор запросов также выберет подходящие индексы для фильтрования и объединения данных в отчете, по-прежнему освобождая вас от дум о связях, ключах и индексах везде, где это возможно. В результате запросы выполняются примерно так, как они бы выполнялись в реляционной базе данных с настроенными вручную индексами.
Лирическое отступление про оптимизацию
Обычно система обрастает индексами и оптимизациями по мере появления проблем, при этом (как правило, в спешке) выделяется самое проблемное место и лечится именно оно. Менее проблемные места продолжают потреблять ресурсы, создавая нагрузку на сервер в таком большом количестве мест, что лечить их точечно достаточно дорого, и часто это таит дополнительные риски. В результате у нас есть время подумать об этом в те бесконечные секунды, когда загружается страница корпоративного портала, JIRA или крутится AJAX-spinner интернет-банка.
Интеграл устраняет эти точечные неэффективности автоматически, оставляя лишь объективно существующие проблемы. Разумеется, оптимизация бывает необходима. Например, если история поставок содержит несколько миллионов партий, то имеет смысл ввести признак «актуальна» для тех партий, где еще остались позиции, чтобы не просматривать все партии при подсчете остатков. Если это «забыли» сделать на этапе проектирования, то это легко исправить позже, и такое решение может принять только программист. Пользователь крайне редко способен на такой анализ и решение, а Интеграл тут и вовсе бессилен.
Эта лирика к тому, что программист необходим, и никакой конструктор не заменит его опыт и знания, но лучше, если он занимается стратегическими вещами, а не ловлей блох в журналах и точечными исправлениями примитивных и глупых недочетов. Много рутинной работы устранит индексирование Интеграла.
Интеграл устраняет эти точечные неэффективности автоматически, оставляя лишь объективно существующие проблемы. Разумеется, оптимизация бывает необходима. Например, если история поставок содержит несколько миллионов партий, то имеет смысл ввести признак «актуальна» для тех партий, где еще остались позиции, чтобы не просматривать все партии при подсчете остатков. Если это «забыли» сделать на этапе проектирования, то это легко исправить позже, и такое решение может принять только программист. Пользователь крайне редко способен на такой анализ и решение, а Интеграл тут и вовсе бессилен.
Эта лирика к тому, что программист необходим, и никакой конструктор не заменит его опыт и знания, но лучше, если он занимается стратегическими вещами, а не ловлей блох в журналах и точечными исправлениями примитивных и глупых недочетов. Много рутинной работы устранит индексирование Интеграла.
Такое индексирование требует больше места для хранения данных, чем выборочно индексированная база (в 2-3 раза), однако экономит силы и нервы при разработке и поддержке, что в деньгах получается несравнимо дешевле. Эти накладные расходы увеличиваются линейно, а в ряде случаев получаются меньше, чем расходы на хранение данных в базе с множеством составных индексов, в платформе 1С с её GUID и денормализацией или, тем более, в CMS вроде Битрикс с его инфоблоками.
Чтобы не разводить теоретический холивар про индексы и производительность, мы развернули тестовый стенд, где можно посмотреть и потрогать, как это работает на нескольких миллионах записей. Этот стенд подтормаживает иногда, как и любая база с миллионами записей и десятками одновременных запросов, поэтому адекватное представление о производительности там получить можно.
Интеграл обеспечивает минимальные средства поддержания целостности данных, например, не позволяя удалить справочное значение, если оно используется в качестве реквизита. То же самое относится к типам и реквизитам объектов — нельзя удалить тип или реквизит, если имеются его экземпляры или он упоминается в запросе или роли. Также есть проверка на уникальность добавляемых объектов, но только в самом простом случае: если добавляется неуникальное значение объекта, отмеченного уникальным в Редакторе типов.
Из ядра Интеграла намеренно вынесены такие вещи как триггеры, ключи, constraint’ы и многое другое. Также было скрыто всё, что было возможно скрыть от пользователя, например, системные ID записей (это бывает непривычно для программиста, но достаточно быстро усваивается). Тем не менее, при желании любой ID можно достать из системы, а в ряде случаев это просто необходимо, для общения по API, например.
Следует заметить, что Интеграл развивается не в сторону усиления технических барьерных средств, вроде механизма транзакций, блокировок и ограничений, а больше поощряет составление бизнес-процесса таким образом, чтобы избежать коллизии в принципе, имея прозрачную картину происходящего. Образно, чем бить по рукам в случае ошибки, лучше просто не дать пользователю ошибаться. Эта скользкая тема будет обсуждена в отдельной статье, подкрепленной более конкретной теорией и примерами из жизни.
Поля данных: подробнее
Рассмотрим в общих чертах назначение реквизитов Полей данных:
Имя в отчете — название колонки в выводимом отчете, по умолчанию — название объекта, указанного в Поле данных.
Формула задает вычисляемое выражение или псевдоним колонки. Здесь можно использовать значения других полей, произвольные операторы и функции, допустимые в языке SQL (той базы данных, на которой развернут Интеграл, в данной статье это MySQL), а также вложенные запросы. Интеграл предоставляет доступ к очень мощным и гибким средствам для различных вычислений.
Значение (от), Значение (до) — диапазон значений (для дат и чисел) или маска (для текста). Правила фильтрования и другие возможности использования этих полей подробно описаны в Руководстве пользователя Интеграл.
Функция — функция, применяемая к значению поля. Может быть агрегирующая: AVG (среднее), COUNT (количество), MAX (максимальное), MIN (минимальное), SUM (сумма), тогда группировка значений, посчитанных этой функцией, происходит по всем полям, в которых не используется агрегирование (не указана одна из перечисленных функций).
Функция (от), Функция (до) — диапазон значений (для дат и чисел) или маска (для текста), применяемые после вычисления функций, указанных в поле Функция (аналог ключа HAVING в SQL), в остальном работает аналогично реквизиту Значение.
Скрыть — если поставить галку в этом поле, то соответствующая колонка не будет отображена в отчете, хотя ее значение может использоваться для вычислений, фильтрования и сортировки отчета.
Формат — конечный формат отображения поля. Служит для приведения значения к нужному виду.
Сорт. — указатель последовательности и направления сортировки отчета. Отрицательное число здесь даст обратную сортировку (по убыванию значений).
Итог — агрегирующая функция, применяемая к значениям колонки отчета. Если указана функция хотя бы в одной колонке, то в отчете выводится итоговая строка с соответствующими значениями.
Присвоить — см. следующий раздел.
Alias — используется при ручном объединении таблиц для разделения их под разными именами.
Имя в отчете — название колонки в выводимом отчете, по умолчанию — название объекта, указанного в Поле данных.
Формула задает вычисляемое выражение или псевдоним колонки. Здесь можно использовать значения других полей, произвольные операторы и функции, допустимые в языке SQL (той базы данных, на которой развернут Интеграл, в данной статье это MySQL), а также вложенные запросы. Интеграл предоставляет доступ к очень мощным и гибким средствам для различных вычислений.
Значение (от), Значение (до) — диапазон значений (для дат и чисел) или маска (для текста). Правила фильтрования и другие возможности использования этих полей подробно описаны в Руководстве пользователя Интеграл.
Функция — функция, применяемая к значению поля. Может быть агрегирующая: AVG (среднее), COUNT (количество), MAX (максимальное), MIN (минимальное), SUM (сумма), тогда группировка значений, посчитанных этой функцией, происходит по всем полям, в которых не используется агрегирование (не указана одна из перечисленных функций).
Функция (от), Функция (до) — диапазон значений (для дат и чисел) или маска (для текста), применяемые после вычисления функций, указанных в поле Функция (аналог ключа HAVING в SQL), в остальном работает аналогично реквизиту Значение.
Скрыть — если поставить галку в этом поле, то соответствующая колонка не будет отображена в отчете, хотя ее значение может использоваться для вычислений, фильтрования и сортировки отчета.
Формат — конечный формат отображения поля. Служит для приведения значения к нужному виду.
Сорт. — указатель последовательности и направления сортировки отчета. Отрицательное число здесь даст обратную сортировку (по убыванию значений).
Итог — агрегирующая функция, применяемая к значениям колонки отчета. Если указана функция хотя бы в одной колонке, то в отчете выводится итоговая строка с соответствующими значениями.
Присвоить — см. следующий раздел.
Alias — используется при ручном объединении таблиц для разделения их под разными именами.
Запросы на изменение данных
Интеграл умеет вносить изменения в данные, выбранные в результате запроса. Его архитектура позволяет так делать, поэтому грех не воспользоваться возможностью. По умолчанию изменения производятся в два этапа: предварительный просмотр предстоящих изменений, затем подтверждение. Также можно выполнить запрос без подтверждения.
В конструкторе отчетов у Поля данных есть реквизит «Присвоить». В этот реквизит можно записать какое-либо значение, будь то константа или выражение, вычисляемое из значений других полей этого же запроса. Это выражение будет вычислено для каждой записи, возвращенной запросом, и присвоено тому полю данных, для которого оно указано.
Запрос на изменение может создавать, изменять и удалять данные. Это бывает удобно, например, если вам необходимо запланировать встречи с клиентами, провести биллинговые операции или просто удалить старые записи. Посмотрим, как это работает на примере небольшой структуры данных:
Изменение данных
Для начала проставим примечание «Осень 2017» для всех клиентов, с кем были заключены договоры осенью 2017 года. Для этого составим запрос, отбирающий таких клиентов, и выглядеть он будет примерно так:
Запрос отбирает нужные данные, применяя к дате договора фильтр с 1 сентября по 30 ноября 2017 года. В результате получится следующий отчет:
Теперь для этих клиентов зададим новое значение реквизита Примечание (реквизит «Присвоить»), а также дадим именам колонок более симпатичные заголовки:
Запустив наш обновленный отчет, получим подсказку, какие изменения будут внесены в данные этим запросом:
В появившейся колонке «Выполнить» мы видим, как будут изменены данные, и, похоже, мы можем потерять старое значение примечания к клиенту Дмитрий М. Чтобы не допустить этого, изменим реквизит «Присвоить», применив функцию языка SQL, которая сцепит существующий текст с новым. Мы присвоим псевдоним MEMO этой колонке, и по этому псевдониму сможем подставить ее значение в функцию CONCAT(), сцепляющую фрагменты текста:
Теперь запрос сделает точно то, что мы хотим:
По нажатию «Выполнить» запрос внесет изменения в базу данных, выдав соответствующий отчет:
В Словаре, в таблице клиентов мы увидим новые данные по клиентам:
У вас может возникнуть вопрос: что будет, если какой-то объект встречается в отчете больше одного раза, и мы пытаемся его изменить. Ответ: объект будет изменен только 1 раз. Например, в следующем отчете мы добавляем Примечание клиента, который упомянут в нескольких строках отчета, и Интеграл выполнит изменение только 1 раз для каждого клиента:
Это достаточно простой пример использования запросов на изменение данных. На практике может понадобиться обновить несколько реквизитов в различных таблицах, например, обфусцировать (обезличить) персональные данные клиентов перед выгрузкой данных на тестовый сервер.
Сервис позволяет указать новые значения для нужного количества полей данных, в том числе из разных сущностей, — Интеграл аккуратно разнесет все эти изменения по нужным таблицам.
Добавление записей
Теперь продемонстрируем создание новых записей. Пример: нам нужно запланировать на полдень 1 февраля 2018 года встречу со всеми клиентами в статусе «Клиент». Создадим такой запрос:
Получившийся из него отчет выведет подсказку о создаваемых записях, а по нажатию «Выполнить» будут добавлены записи о встречах, при этом повторный запуск запроса не приведет к дублированию уже созданных записей, если таковые имеются.
В Словаре во Встречах Клиентов появятся эти записи:
Удаление записей
Для удаления записей достаточно присвоить им пустые значения — "". Пример удаления записей по встречам весьма прост:
Интеграл не тратит место на пустые значения, поэтому записи будут просто удалены, со всеми своими реквизитами и подчиненными таблицами, если таковые есть:
Использование запросов к внешним источникам
Интеграл позволяет обратиться к внешним системам (сайты, API, файлы и т.д.) по протоколу http(s) во время выполнения запроса. Для этого необходимо задать шаблон URL внешнего источника в поле URL отчета (в этом примере я переименовал тип Запрос в Отчет в Редакторе типов). В шаблоне можно использовать значения полей отчета, заключив их имена в квадратные скобки.
Далее при выборе колонок отчета можно задать им имена, и значения этих колонок будут подставлены в шаблон URL:
Функция abn_URL заменит содержимое своего поля на результат выполнения запроса к полученному URL, вычисленному для каждой строки отчета. В результате выполнения, например, заданного выше запроса к API поставщика SMS-сервиса мы получим ответ сервиса с идентификатором отправленного сообщения, которое придет на заданный нами телефон [phone] получателю с именем [name]:
Результат запроса к URL можно также сохранить в базе, присвоив его значение какому-либо полю.
Пример задачи с составными типами
Напоследок хотелось бы привести пример, который почему-то сильно впечатлил нашего клиента из среды 1С — задача про составные типы. Задача была сделать отчет об операциях в разрезе разных аналитик, состав которых зависел от набора счетов в операции. Для этого отчета пришлось написать немного javascript в пользовательском интерфейсе для простоты восприятия, формы со всем этим безобразием (сверстано быстро и грязно, простите) доступны на Гитхабе: Aggregate Types.
Итак, дан план счетов с указанием набора аналитик для каждого счета (на рисунке — фрагмент плана):
План счетов был импортирован в Интеграл, а объекты аналитик — типы, имеющие набор реквизитов, были созданы в Редакторе типов, например:
Номенклатура — иерархический справочник со ссылкой на родительский элемент.
Далее в Редакторе типов мы создали тип Операция со счетами Дт и Кт, для которых в свою очередь задан допустимый набор объектов аналитики:
Незаполненные реквизиты объекта не занимают место в базе данных. Если набор объектов аналитики очень большой, то можно не загромождать ими список реквизитов, а создавать эти типы с определенным реквизитом, допускающим добавление в состав сложного типа. Хотя можно даже и без этого обойтись и добавлять всё, к чему у пользователя есть доступ.
При заведении операций можно задать аналитику согласно вовлеченным счетам и её фактическим параметрам:
Далее мы формируем отчет по операциям, где можно выбрать произвольный набор интересующих нас аналитик отдельно для счетов Дт и Кт. По каждой аналитике можно задать условия отбора записей (поле Фильтр):
Мы не хотим заставлять конечного пользователя работать с базовым интерфейсом Интеграла даже в прототипе его приложения, поэтому сверстали для него такую форму ввода. На самом деле во время задания аналитик добавляются и удаляются записи в Поля данных Запроса «Отчет по аналитикам», и вот как это же выглядит в Словаре:
Этот отчет выберет все операции, которые имеют требуемый набор аналитик, удовлетворяющий условиям отбора. Конечно, мы можем включить в отчет реквизиты объектов аналитики на всю их глубину, когда заказчику это понадобится.
Результат отчета — это сумма операций для каждой комбинации аналитик:
Бизнес-ориентированность
Основная задача Интеграла для широкого пользователя: дать ему возможность ворочать своими данными напрямую, не применяя или даже не зная языков программирования и теории баз данных. Так в свое время появился язык SQL, который предлагал любому бухгалтеру самостоятельно работать с данными на человеческом языке. Мы идем тем же путем, но стараемся обойтись и вовсе без языка и теорий, поскольку любая необходимость делать нечто, не касающееся бизнеса, отталкивает человека от использования инструмента автоматизации.
Девиз Интеграла: «не сложнее, чем в Excel». Интеграл может заменить Excel более гибким и защищенным средством хранения и обработки данных. Кто-то переживает, что это SaaS? Спросите его, не пересылает ли он по почте свои эксели и не хранит ли их в гуглодоксах, то есть сервисах, которые профессионально умеют шерстить данные, выуживая из них всю нужную информацию для себя и уполномоченных служб?
В первую очередь это сервис небольшого бизнеса, в котором зачастую владельцу приходится самому вести разработку или как минимум управлять ею. IT-составляющая критически важна для компании, поскольку именно она определяет маневренность и гибкость бизнеса. Мы стараемся дать возможность начать работу сразу, не отвлекаться на непрофильные темы и смочь всё в корне переделать при необходимости. Поменялся принцип учета? Выверни всю модель наизнанку, выкинь лишнее и добавь нужное! Интеграл позволяет сделать это быстро и достаточно наглядно.
Зачем всё это нам?
Программисты всегда мечтали меньше программировать банальные вещи, чтобы было больше времени для великих свершений.
Наш проект — попытка сделать инструмент для этого, и команде Интеграла удалось добиться его практической работоспособности на тех объемах данных, которые могут встретиться в прикладной разработке нижнего и среднего ценового сегмента. Под «практической работоспособностью» имеется в виду такие критерии:
- темп деградации производительности с ростом объема и сложности не выше, чем у информационной системы, сделанной классическим способом, и заметно лучше, чем у существующих конструкторов;
- отсутствие ограничений, свойственных «конструкторам», в плане создания и использования модели данных;
- сниженный порог вхождения с точки зрения знания языков программирования (знание архитектурных основ информационных систем, наконец, занимает здесь должное место, тесня в сторону экспертизу в сиюминутных технологиях).
Ядро Интеграла достаточно компактно, а низкоуровневая работа с данными по нашим расчетам может быть вообще выполнена в железе. Прощайте, Meltdown и Spectre; здравствуй, импортозамещение… Неожиданно? А между тем в IT уже давно не происходит ничего принципиально нового (да простят меня апологеты квантовых компьютеров, но до практического применения им еще далеко), и плох тот мечтатель, который не стремится сделать что-то эдакое на практике. Архитектура сервиса в корне отличается от существующих платформ, и, мы верим, может изменить мир IT, как изменило его, например, появление открытой архитектуры IBM PC.
Сервис активно развивается только последние пару лет, и мы видим, что при таком небольшом (и нестабильном, ведь всем нужны деньги) коллективе нам до нормального продукта работать еще декаду, а команде уже приходится жертвовать многим для продолжения проекта. При этом в самом направлении мы видим несомненные перспективы, что, в частности, побудило написать эту статью — мы хотим сотрудничать со всеми, кто готов развивать, улучшать и популяризировать подход Интеграла.
Ссылки
Руководство разработчика
Регистрация своего ознакомительного экземпляра базы на сервисе: https://tryint.ru