Два уровня MVC при проектировании
прогрессивных веб-приложений (PWA)

Статья длинная и без картинок. Прежде, чем читать, можно посмотреть пример сервера, на котором 7 моделей 1 уровня с помощью 7 представлений 1 уровня, генерируют 7 приложений, использующих один и тот же скрипт на React. Код на github.

О прогрессивном веб-приложение

Я не буду описывать web-manifes, - есть много хороших публикаций: обратитесь к ним.

Меня не интересуют приложения, работающие оффлайн, и мне вполне хватает кэша браузера, хотя принцип построения приложения хорошо вписывается в Service Worker .

В этой статье я постараюсь описать принцип разделения кода и данных при создании сложных приложений.

Идея не нова:

1. Сервер (1-й уровень MVC)

От клиента в http-API сервера поступает запрос, ответом на который должна быть html-страница. В моем случае страница должна содержать:

Кроме данных все кэшируется браузером.

Тоже самое поподробней:

Структура строницы. Манифест документа.

В качестве примера приведу страницу http://react-py.ru/newdoc?&home

newdoc?&home - создать новый документ с формой home.

<!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>React-py</title> <link rel="shortcut icon" href="/favicon.ico"> <link rel="stylesheet" href="jsv?api/react.js/sova-main.css::22.03.2020-21:13:22"> <script> window.jsDoc={

"rsMode": "edit",
"dbAlias": "",
"unid": "",
"printList": "",
"data": {"FORM": "home", "_PAGE_": "1"},
"urlForm": "api.getc?loadForm&home::3133656032",
"multiPage": "",
"userName": "",
"cssJsUrl": [

"jsv?api/views/Outline/outline.css::18.07.2018-16:44:17",
"jsv?api/forms/home/home.css::04.05.2019-12:32:03",
"jsv?api/forms/home/home.js::18.03.2020-13:17:02"

]};

</script> </head> <body> <div id="root"></div> <script src="jsv?api/react.js/runtime-main.js::17.01.2020-19:50:32"></script> <script src="jsv?api/react.js/react16-8.js::17.01.2020-19:50:32"></script> <script src="jsv?api/react.js/sova-main.js::22.03.2020-21:13:22"></script> </body> </html>

У меня все приложения открываются с аналогичной страницей. Страницы создаются по шаблону, в который Python подставляет 2 строки: значение тега title и манифест документа window.jsDoc.

Приложение может открыть несколько документов на одной вкладе. В этом случае ему достаточно получить манифест документа в json-формате.
Пример http://react-py.ru/newdoc?&mp_spa

Контроллер 1-го уровня.

Контроллер - это клиент. API отправило ему html-страницу, которую сформировало представление и забыло.

Если клиент захочет сохранить данные в базе, придется вспомнить.

Последовательность обратная: клиент отправляет в json-формате значения полей и имя формы.

АПИ вызывает метод querySave, по имени формы определяя нужный класс.

Метод может сделать дополнительную валидацию, что-то в данных подправить.

Если querySave вернула True, данные передаются модели для записи в БД. При успешном окончании записи клиент получает долгожданный код 200.

В классе формы может быть метод postSave, вызываемый после записи в БД и ответа клиенту. В нем можно отправить данные для индексирования и т.д.

----
MVC хорош тем, что он разделяет зоны ответственности. Модель вернула данные и не вмешивается в действия представления.

Модель может вернуть готовую html разметку. Для такой разметки есть замечательная форма, которая ничего не делает, просто отправляет в контроллер то, что получила.

Если заказчик решил заменить таблицу на график, - нет проблем. Удаляем форму, делаем новую хоть на React, хоть на Plotly-Dash и отправляем в контроллер. Пусть сам разбирается.

2. Клиент (2-й уровень MVC)

Клиент получает данные в виде keys-values, будем считать это моделью, и url формы, она же представление.

В нашем случае форма - это json строка, в которую упакованы контроллеры и элементы разметки. Каждый контроллер имеет имя (имя поля). Он умен (хранит состояние в себе), красив и объектноориентирован.

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

Мы пошли другим путем. Каждая открытая на экране форма – это объект класса Document (назовем его doc). После загрузки doc имеет:

Объект doc по url получает от сервера или из кэша форму в виде json строки, парсит ее в React-компоненты и отправляет на рендеринг. При рендеринге в doc создается реестр контроллеров {fieldName: control, …}. Я надеюсь, ref не запретят. Запретить ref, - все равно, что запретить указатели в плюсах.

Контроллер.

Middleware

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

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

Для каждой формы свои. Там и находится middleware.

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

Скрипты написаны на чистом JS. Чтобы обеспечить связь подгружаемых скриптов формы с объектами класса Document, введена 1 глобальная (ужас) переменная.

Структура скрипта: