Урок 15. Режимы загрузки данных
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Пришло время разобраться, как происходит загрузка данных с сервера и какие возможности есть у разработчиков, чтобы контролировать этот процесс.
Платформа Workflow Technology поддерживает три режима загрузки данных:
Обеспечивает минимальную скорость загрузки форм, при этом требует минимальное количество ресурсов со стороны сервера.
Обеспечивает стандартную скорость загрузки форм, при этом не требует значительного количества ресурсов со стороны сервера.
Обеспечивает увеличенную скорость загрузки форм, однако при этом требует максимальное количество ресурсов со стороны сервера. Режим по умолчанию.
Эти режимы описаны в таблице public.load_mode, а в поле selected этой таблицы можно выбирать текущий режим.
Эти режимы работают только в момент открытия формы и первичной загрузки данных в PrimaryGetDataConnection, не имеющих тэга <ManualLoad>
со значением True. Еще они не влияют на работу команды DataConnectionRefreshCommand.
Платформа при загрузке формы строит дерево зависимостей для запросов в DataConnection (в одном DataConnection может быть несколько запросов). При этом учитываются все связи между ними (прямые и косвенные через команды и объекты). На основе этого дерева строится порядок выполнения запросов. Первыми всегда будут выполняться те запросы, которые не зависят от результата выполнения других запросов.
В разделе Режим загрузки данных (LoadMode) на примере карточки заказа подробнее рассмотрим работу каждого режима. Но, сначала скорректируем форму настроек, добавив возможность из интерфейса программы менять режим загрузки данных на формы, и познакомимся с утилитой WorkflowVisualizer, с помощью которой сможем отследить изменение порядка загрузки данных при переключении режима.
Форма настроек
Менять режим загрузки данных необходимо, чтобы разгрузить сервер в том случае, когда количество клиентских частей выросло и вырос объем данных. Вынос настройки в базу данных позволяет разом перевести все клиентские части на новый режим, так как перевод одной части не принесет существенного ускорения работы сервера.
Очень удобно, когда пользователь имеет возможность самостоятельно выбирать режим загрузки данных и искать тот, который наилучшим образом ускорит работу. Давайте реализуем такую возможность, добавив на форму "Настройки" поле "Режим загрузки данных". При этом отделим настройки почтового агента от настроек загрузки данных, вынеся их на отдельную вкладку.
В результате должна получиться форма вида:
Вкладки
В платформе нет отдельного объекта "Вкладки", вместо этого используются обычные кнопки (Button), которые изменяют видимость панелей (Panel).
Перейдем в файл TemplateSettings.xml.
Добавим цвет, которым будем подсвечивать вкладку при наведении на нее курсор мыши:
Создадим переменную Variable, в которой будем хранить значение активной вкладки:
Так как на форме настроек у нас будет две вкладки: основные настройки (General) и настройки почтового агента (Email), то создадим два условия:
Создадим команду ValueSetCommand для изменения значения активной вкладки:
Теперь можем создать сами кнопки, которые будут выступать в качестве вкладок. Добавим описание кнопок в панель HeadPanel:
Сократим ширину объекта HeadLabel, чтобы он не перекрывал кнопки.
Переименуем ContentPanel в EmailPanel и создадим GeneralPanel. Добавим в панели условия видимости с соответствующими Condition.
Запустите приложение и проверьте загрузку формы настроек и отображение вкладок:
Элемент выбора режима
Перейдем в серверный xml-файл и добавим запрос:
В этом запросе применяется функция public.string_value(character varying, integer), которая предназначена для получения строкового ресурса из таблицы public.strings. Для этого функция использует ключ ресурса (strings_id) и системную переменную {PublicUserId}
(идентификатор пользователя). По идентификатору пользователя из таблицы public.user получаем идентификатор языка (language_id).
В следующих уроках мы подробно рассмотрим поддержку языков, многопользовательский режим и особенности работы с таблицей public.strings, в которой пока хранятся только системные ресурсы.
Добавим новый запрос в SettingsViewSqlQueryPermission.
Скорректируем запрос SettingsUpdateSqlQuery, добавив сохранение выбранного режима загрузки данных:
Вернемся в файл формы настроек (TemplateSettings.xml) и создадим загружающее соединение с данными, которое будем использовать в тэге <ValueList>
объекта LoadModeComboBox:
Так же создадим вторичное соединение с данными, в котором получим запись активного режима загрузки данных и будем передавать его идентификатор в тэг <Value>
объекта LoadModeComboBox:
Создайте выпадающий список "Режим загрузки данных" (LoadModeComboBox), для которого запретите ввод пустого значения (NullValue = False).
Добавим Label с описанием режима, чтобы пользователю была доступна информация об особенностях выбранного режима загрузки данных.
Для начала добавим цвет для надписи:
А также добавим стиль шрифта:
Добавим вторичное соединение с данными, в котором будем фильтровать записи относительно выбранного в ComboBox режима:
Создадим LoadModeDescriptionLabel:
Откроем форму настроек и проверим расположение элементов:
Добавим уведомление пользователя о необходимости перезапустить сервер и клиентскую часть при изменении режима загрузки данных.
Создадим условие, в котором будем сравнивать значение режима загрузки, полученное из базы данных, и значение из выпадающего списка LoadModeComboBox, выбранное пользователем на форме:
В условии LoadModeIsChangedNotEqualCondition можно проверять свойство ValueChanged объекта LoadModeComboBox, но сравнивая два значения, мы будем отлавливать точные изменения значения в базе данных без лишних срабатываний.
Создадим команду для отображения уведомления:
Добавьте вызов LoadModeChangedMessageBoxCommand в команду SaveSequentialCommand сразу после вызова команды SettingsSaveCommand.
Запустите приложение и проверьте сохранение данных на форме.
Теперь можем познакомиться с утилитой WorkflowVisualizer.
Отладка приложений
Текущая версия редактора Workflow XML Editor не позволяет протестировать загрузку форм и выполнение команд. Для таких целей существует утилита WorkflowVisualizer.
С помощью утилиты можно отслеживать диаграмму выполнения запросов и время загрузки формы при разных режимах загрузки данных. Также она позволяет проследить корректность и порядок обновления данных, срабатывания команд и условий. Утилита дает возможность обнаружить лишние или циклические срабатывая и обновления.
Настройки
Утилита читает файлы логов от приложения и на их основе строит диаграммы. Чтобы приложение сохраняло логи, необходимо включить режим отладки (DebugMode) и указать каталог (DebugPath), в который будут писаться логи. Для сервера и форм режим отладки включается отдельно.
Чтобы серверная часть писала логи, необходимо в файл appsettings.json добавить поля:
Для включения режима на клиентской части в файл WorkflowForms.dll.config добавляются настройки:
Перезапустите сервер и приложение, проверьте наличие папки с файлами логов.
WorkflowVisualizer
Запустим приложение WorkflowVisualizer.
В поле DebugFolder укажем путь до нашего каталога и нажмем на кнопку "Загрузить". WorkflowVisualizer подхватит файлы логов и построит диаграмму загрузки PrimaryGetDataConnection:
Полнота диаграммы настраивается галочками:
SqlQuery - время обновления PrimaryGetDataConnection и конкретного SqlQuery на форме;
SqlQueryEngine - время исполнения запрос на контроллере сервера;
SqlQueryReader - время обращения сервера к базе данных;
PrimaryGetDataConnection - время обновления PrimaryGetDataConnection на форме. Обычно тут не бывает элементов, так как они слиты с SqlQuery;
SecondaryDataConnection - время обновления SecondaryDataConnection;
ArrayDataConnection - время обновления ArrayDataConnection;
ConvertDataConnection - время обновления ConvertDataConnection;
Condition - изменение Condition;
Execution - сработавшие Execution на форме;
Command - сработавшие команды;
OnSuccess - время, связанное с обновлением всех подписанных на DataConnection объектов;
Unknown - все остальные.
Элементы SqlQueryEngine и SqlQueryReader будут отображаться только в том случае, если в серверном файле конфигурации включен режим отладки DebugMode.
Давайте поставим галочки SqlQuery, SqlQueryEngine и SqlQueryReader и посмотрим, как изменится диаграмма:
Отлично! Теперь можем подробнее поговорить про режимы загрузки данных и увидеть, как будет меняться диаграмма при смене режима. Режимы будем рассматривать на примере карточки заказа.
Режим загрузки данных (LoadMode)
Каждый раз меняя режим загрузки данных на форме настроек, не забывайте перезапускать клиентскую часть, чтобы применились настройки.
Режим 0 - последовательный
В последовательном режиме все запросы будут выполняться друг за другом в порядке описания PrimaryGetDataConnection в xml-файле. Отправив один запрос, форма будет дожидаться ответа от сервера и затем отправлять следующий запрос.
На диаграмме видно, что ClientPrimaryGetDataConnection, зависящий от поля ClientId соединения OrderPrimaryGetDataConnection, загрузился последним. А остальные загрузились в порядке описания в файле.
Узким местом такого режима будет наличие тяжелого запроса, который будет тормозить загрузку формы, хотя в момент его выполнения сервер может параллельно обрабатывать другие запросы, которые не зависят от его результатов.
Искусственно утяжелим запрос OrderByIdSelectSqlQuery, добавив функцию pg_sleep() с задержкой на 2 секунды.
Открыв повторно карточку заказа, на диаграмме увидим картину:
На блоках OrderPrimaryGetDataConnection, OrderByIdSelectSqlQueryEngine и OrderByIdSelectSqlQueryReader пропал темный прямоугольник. Теперь блоки полностью отображают время обновления DataConnection и обработки запроса на стороне сервера и базы данных.
В такой ситуации ждать результата OrderPrimaryGetDataConnection необходимо только для ClientPrimaryGetDataConnection, так как он зависит от значения в поле ClientId. А остальные DataConnection можно запустить параллельно, чтобы не терять время в пустом ожидании.
Режим 1 - пакетный
В пакетном режиме после построения дерева зависимостей запросы разбиваются на пакеты. В первый пакет попадают все запросы, которые ни от кого не зависят, во второй пакеты - запросы, которые зависят от запросов из предыдущего пакета, и так далее.
На диаграмме видим, что в первый пакет попало шесть запросов. Запрос ClientShortSelectSqlQuery отправился на сервер во втором пакете после того, как выполнился самый тяжелый запрос OrderByIdSelectSqlQuery первого пакета.
Зависимостью можно управлять через тэг <DependOn>
, указав в нем список DataConnection, которые должны быть выполнены раньше текущего.
Для OrderPositionPrimaryGetDataConnection и OrderPaymentPrimaryGetDataConnection укажем зависимость от OrderPrimaryGetDataConnection и посмотрим на диаграмму:
В первом пакете осталось четыре запроса, а во второй пакет к ClientShortSelectSqlQuery попали OrderPositionByOrderIdSelectSqlQuery и OrderPaymentByOrderIdSelectSqlQuery.
На диаграмме включили новые объекты OnSuccess, чтобы видеть моменты, когда форма получила результаты от сервера. В нашем случае ответы о выполнении всех запросов первого пакета пришли по завершении самого тяжелого запроса.
Таким образом, узким местом этого режима так же является наличие тяжелого запроса, который будет тормозить отправку следующего пакета.
Режим 2 - параллельный
В этом режиме так же строится дерево зависимостей, после чего формируется первая волна независимых запросов, которые параллельно отправляются на сервер. Следующие запросы не будут ждать выполнения всех запросов из первой волны, а будут отправляться на сервер по мере того, как будут готовы результаты всех запросов, от которых они зависят.
Для OrderPositionPrimaryGetDataConnection поменяем зависимость на MaterialPrimaryGetDataConnection. А с OrderPaymentPrimaryGetDataConnection удалим зависимость. Искусственно утяжелим запрос MaterialSimpleSelectSqlQuery.
В WorkflowVisualizer увидим картину:
После того как на сервер ушли все запросы первой волны, ClientPrimaryGetDataConnection не стал дожидаться завершения MaterialPrimaryGetDataConnection, а сразу же ушел на сервер, как только был готов результат OrderPrimaryGetDataConnection. В то время как OrderPositionPrimaryGetDataConnection дожидался завершения запроса, от которого зависит и отправился в третьей волне.
Ручное управление загрузкой данных
Есть разные возможности, которые позволяют разработчику контролировать порядок загрузки данных при старте формы помимо выбора режима LoadMode. С одной такой возможностью мы уже познакомились - тэг <DependOn>
.
DependOn
Управление зависимостью через тэг <DependOn>
в описании PrimaryGetDataConnection - так мы можем контролировать построение дерева зависимостей в момент загрузки формы.
ManualLoad
Можно перевести все PrimaryGetDataConnection в режим ручной загрузки, добавив вложенный тэг <ManualLoad>
со значением True. DataConnection с ручной загрузкой не будет выполняться в момент загрузки формы. Для его обновления при старте формы необходимо вызвать команду DataConnectionRefreshCommand в Execution по условию FormLoadedCondition.
DataConnectionRefreshCommand
В команде DataConnectionRefreshCommand все DataConnection можем разбивать на пакеты, используя атрибуты Packet
(разрешает загрузку DC в пакете) и PacketGroup
(текстовая метка пакета):
При разбиении DataConnection на пакеты в команде типа DataConnectionRefreshCommand необходимо придерживаться следующих правил:
Имена пакетов, указанные в атрибуте
PacketGroup
, являются текстовой меткой пакета и не определяют порядок выполнения пакетов. Порядок выполнения пакетов задается порядком упоминания метки пакета в команде;DataConnection из одного пакета лучше описывать друг за другом, чтобы не создавать путаницы;
В первый, в порядке объявления, пакет следует указывать запросы, которые не зависят от других запросов;
В следующий, в порядке объявления, пакет должны попадать запросы, которые зависят от запросов из предыдущего пакета;
Если в команде не указано явное разбиение на пакеты, то считается, что все запросы находятся в одном пакете.
В качестве эксперимента на форме карточки заказа переведем все PrimaryGetDataConnection в режим ручной загрузки (ManualLoad = True) и создадим команду, в которой все DataConnection разобьем на четыре пакета:
Вызовем эту команду в Execution, предварительно описав системное условие FormLoadedCondition:
При загрузке формы увидим примерную диаграмму:
Здесь можем наблюдать повторное обновление ClientPrimaryGetDataConnection. Первое обновление произошло автоматически из-за изменения параметра ClientId, которое было вызвано обновлением OrderPrimaryGetDataConnection.
Чтобы избежать автоматического обновления данных при изменении значения параметра, следует использовать атрибут RefreshQuery
со значением False.
Давайте добавим такой атрибут для параметра ClientId в ClientPrimaryGetDataConnection и проверим диаграмму загрузки:
Теперь данные загружаются в том порядке, который необходим нам, и без лишних обновлений.
Обновление объектов на форме
Но теперь возникает другая проблема на форме: ClientComboBox теряет свое значение.
Причина этого в том, что OrderPrimaryGetDataConnection обновляется раньше, чем ClientPrimaryGetDataConnection, который является источником данных для выпадающего списка. Таким образом, мы пытаемся подставить в ClientComboBox значение (Value), которого нет в списке (ValueList). И самого списка еще нет на форме. ClientComboBox забывает это значение.
Чтобы такого не происходило, используйте в ComboBox тэг <AllowOutOfList>
:
Вторая особенность поведения объектов на форме при таком обновлении данных заключается в том, что у всех объектов тэг <Change>
по умолчанию имеет значения:
И при загрузке форма будет считать, что все объекты изменились.
Поэтому указывайте тэг <Change>
с нужными значениями атрибутов:
Параллельное выполнение запросов
По умолчанию команда DataConnectionRefreshCommand отправляет все загружающие соединения с данными в рамках одного PacketGroup на сервер одним запросом. Форма дожидается результата обновления всех запросов из пакета и после этого рассылает событие своим объектам для обновления значений.
Зачастую возникает необходимость обновлять данные на форме не дожидаясь результатов долгого запроса. Это обеспечивает плавную отрисовку формы и дает пользователю возможность начать работать на форме.
Для таких случаев команда DataConnectionRefreshCommand имеет вложенный тэг <Parallel>
. Если его значение True, то все запросы в рамках одного PacketGroup отправляет на сервер параллельно. Форма не дожидается результатов всех запросов из пакета, а рассылает событие объектам по мере готовности данных.
Важно отметить, что если в команде не указано явное разбиение на пакеты, то считается, что все запросы находятся в одном пакете:
И при включении параллельной отправки запросов мы получим следующую картину:
Get-проперти LoadMode
У формы есть get-проперти LoadMode, которое возвращает идентификатор выбранного режима загрузки данных. Значение этого get-проперти можно сравнивать со значением 1 - выбран ли параллельный режим загрузки:
А затем использовать это условие в команде DataConnectionRefreshCommand в качестве значения тэга <Parallel>
, чтобы поддерживать параллельный режим загрузки при ручном управлении загрузкой данных:
Итого
В этом уроке мы рассмотрели режимы загрузки данных с сервера, реализовали в приложении настройку, чтобы пользователь через интерфейс мог менять режимы загрузки. Также рассмотрели способ создания вкладок на форме.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, а также бэкап базы данных - с помощью файлов можете проверить себя.
Last updated