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


В статье я хотел бы рассказать о своем пути поиска такой системы.


Сталкиваясь подобными СУБД (клиент-банки, системы ФСЗН, расчетно-кассовые приложения) с точки зрения пользователя, я всегда удивлялся, почему даже крупные компании с достаточными финансовыми возможностями часто не могут реализовать действительно удобный интерфейс в своих приложениях.


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


1С: Предприятие


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



На тот момент в этом подходе меня все устраивало. Однако через некоторое время я столкнулся с потребностью в сетевом доступе к системе учета. Проанализировав цены на серверные части 1С, я понял, что для меня они слишком высоки, и стал искать альтернативу, желательно бесплатную и с открытым исходным кодом.


Django Admin


После анализа и изучения отзывов я выбрал фреймворк Django с его генератором admin-интерфейсов. При этом пришлось перенести некоторые идеи, заложенные в 1С, на код Python. В итоге получались примерно такие интерфейсы:



Однако, при больших объемах данных стали проявляться недостатки такого подхода.


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


Во-вторых, работать с создаваемыми Django Admin интерфейсами было не достаточно удобно, ввод данных был затруднителен и не оперативен. Хотелось интерактивности хотя бы на уровне 1С, чтобы интерфейс не перегружал страницу каждый раз, когда отправляются данные, а использовал такие технологии, как Ajax или WebSocket.


MySQL, Navicat и другие


В результате пришлось полностью отказаться от использования модуля Django Admin и клиентскую часть писать самостоятельно на JavaScript с использованием вышеуказанных технологий. Так был решен вопрос с интерактивностью, но время, которое стало уходить на создание интерфейсов, было неоправданно большим. Иногда, чтобы сэкономить время, для сбора и анализа данных я использовал чистый Mysql с клиентской частью в виде Navicat. Как оказалось, благодаря триггерам и видам, это не самое плохое решение, а огромное число задач решаются таким образом довольно просто (что не удивительно, ведь, согласно википедии, Mysql и создавался первоначально для решения подобных задач).


Критерии оптимального инструмента разработки СУБД


Со временем я сформулировал для себя перечень субъективных пожеланий к инструменту для разработки СУБД. Он должен:


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

Ничего, что бы отвечало этим требованиям, я найти не смог. Поэтому решил написать свой фреймворк, решающий поставленные задачи. В качестве основных технологий были выбраны TypeScript в связке с Angular2. При этом фреймворк проектировался так, чтобы его можно было использовать для разработки приложений не только с применением Angular, но и с помощью других JavaScript-библиотек. Хотелось бы поделиться тем, что получилось: возможно, кому-то пригодится.


Структура нового фреймворка


Фреймворк заточен на быстрое создание интерфейсов для СУБД. Он состоит из нескольких частей (модулей). Некоторые могут использоваться отдельно, некоторые — только совместно с остальными.


Модуль core содержит механизмы описания моделей, взаимодействия объектов (записей) данных между собой, механизмы описания запросов к базе данных. Модуль core обращается к источникам данных через модуль backend.


Модуль backend — это прослойка между модулем core и базой (источником) данных. В качестве источника данных может выступать как непосредственно сервер баз данных, вроде SQL, так и прослойка для доступа к моделям других фреймворков, таких как Django или Sequelize.


Модуль model-ui отвечает за генерацию интерфейса: он визуализирует данные, предоставляемые модулем core, используя элементы управления, предоставляемые модулем ui.


Модуль ui содержит базовые элементы управления, которые используются модулем model-ui при генерации интерфейса. Эти элементы могут использоваться также и независимо от фреймворка.


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


Модели и работа с данными


Особенностью фреймворка, в отличие от того же Django, является то, что для описания списков записей и самих записей используются разные типы моделей: ListModel и RecordModel. Такой подход позволяет в списках отображать записи не только от одной, но и от разных моделей, а также нередактируемые строки (являющиеся, например, результатом работы над этими записями).


Хоть фреймворк и содержит необходимые для этого механизмы, описывать модели при разработке приложения не требуется. Модуль backend автоматически формирует внутренние модели на основании тех, которые уже существуют и описаны в других средах (таких как Django, Sequelize, SQL и иные).


У фреймворка есть сходства работы с Django. Например, классы для работы с выборками и запросами (QuerySet и Query) являются эквивалентами одноименных классов Django, адаптированными из кода Python в код TypeScript. Например, для выборки данных из источника данных необходимо написать примерно следующий код:


backend
  .getListModel('product')
  .getQueryset()
  .filter({name__icontains: 'Штаны'})
  .orderBy('-price')
  .limit(0,10)
  .getRows()
  .subscribe(products => {…});

Ещё одна отличительная особенность фреймворка — поддержка виртуальных полей. Когда изменяется виртуальное поле, оно может менять реальные поля объектов, а когда меняются значения реальных полей, могут изменяться и виртуальные. Что-то похожее есть и в Django, когда через объект мы имеем доступ к данным, не хранящимся в базе данных в том виде, в котором они доступны в этом объекте,— это получение ссылающихся на объект других объектов через свойство xxxxxx_set либо получение доступа к объекту через свойство, когда в базе данных хранится лишь id этого объекта.


На иллюстрации ниже поля product_id и product_name — реальные, а поле product — виртуальное.



Во фреймворке реализована «ленивая загрузка зависимых записей». В отличие от Django, здесь разработчик может решать, в каких случаях этот механизм лучше не применять, а получать данные сразу, тем самым уменьшая количество запросов между клиентом и сервером. Так, в примере выше у продукта product есть поле supplier, которое ссылается на поставщика. По-умолчанию, поставщики будут запрашиваться из базы данных только при обращении к полю product['supplier']. Однако, если вышеприведенный пример модифицировать следующим образом: ...getRows('supplier').subscribe(products => {…}); — то каждый продукт из списка products уже будет содержать данные о поставщике и при обращении к ним не будет происходить запроса к базе данных.


Интерфейс


Для ускорения разработки приложений фреймворк позволяет использовать встроенный интерфейс на базе модулей model-ui и ui. Это удобно, когда нет потребности в специфическом интерфейсе либо необходимо развернуть приложение максимально быстро. Однако, отказавшись от этих модулей, можно использовать и произвольный интерфейс (написанный, скажем, на базе Bootstrap или Angular Material).


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



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


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



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



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



Взаимодействие интерфейса и данных


Во фреймворке большое внимание уделено взаимодействию записей (объектов) между собой.


При изменении записи на форме она автоматически обновляется в списке.



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



В интерфейсе фреймворка также реализован механизм сортировки строк зависимых записей (если это предусмотрено моделью и модулем backend).



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


Планы на будущее


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


Так, например, на данный момент существует backend, который позволяет фреймворку работать только с базой данных MySQL, из-за чего его можно запустить только на Electron. Также не реализован интерфейс для работы на мобильных устройствах и ряд других возможностей.


Ближайшие планы по развитию фреймворка:


  • Реализовать механизмы объединения и группировок в запросах в классе Query.
  • Добавить элементы управления для работы с объединениями и группировками.
  • Разработать backend для преобразования объекта Query в json или xml, а также разработать серверную часть для работы с моделями Django.
  • Реализовать механизм кеширования запросов к серверу данных.
  • Воплотить в жизнь большое количество других идей.

Как попробовать?


Если у вас возникло желание опробовать фреймворк в работе, вы можете посмотреть пример приложения, написанного на нем. Его можно установить следующим образом:


git clone https://github.com/astoniocom/astonio-demo.git

Далее необходимо установить зависимости:


npm install

Затем открыть файл app.module.ts и настроить доступ к любой базе данных MySQL, после чего собрать приложение:


npm run watch

и запустить его командой:


npm run app

Фреймворк доступен на github.


Сайт проекта


Инструкция по созданию приложения на базе фреймворка


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

Комментарии (23)


  1. TyVik
    16.10.2017 21:44

    Круче всех по построению интерфейсов я видел, когда работал в НТЦ «Сонар-Плюс». Там карточки всех объектов могли строиться самостоятельно по БД. То есть поле само знало какого оно типа, с какой ещё таблицой оно связано (можно было ограничивать запросами), добавлять связную таблицу или карточку непосредственно к объекту, сохранять/восстанавливать всё это дело… То же самое касалось и связанных таблиц, которые можно было разместить на одном экране. В общем — полная свобода действий (а ещё оно масштабировалось как в браузере — это уже я делал :) ). Пример, увы, приложить не могу, т.к. давно там не работаю, но старую версию можно посмотреть здесь (извините за ссылку). Я работал в основном уже с GUI версией. По-моему, её демо можно скачать вот здесь. Кстати, сам я тоже работаю с Django, так что большое спасибо за статью!


  1. sshmakov
    16.10.2017 22:27

    Ничего, что бы отвечало этим требованиям, я найти не смог.

    Похоже, что здесь еще присутствовало неявное требование web-интерфейса. Потому что непонятно, почему не рассматривался Qt.

    В любом случае здорово.


    1. sshmakov
      16.10.2017 22:45

      Ах да, и Delphi тоже, конечно


      1. ChymeNik
        16.10.2017 23:08

        и WPF / Windows Forms.


    1. navar Автор
      17.10.2017 00:13

      Да, действительно, требование веб-интерфейса присутствовало (добавил в статью).
      Qt не рассматривался как по этой причине, так и потому, что он, вроде, не позволяет автоматически генерировать интерфейс на основании моделей.


  1. schroeder
    16.10.2017 22:34

    Когда у меня была похожая задача я взял Dvelum. Остался доволен.


  1. pnovikov
    16.10.2017 23:58
    +1

    Смотрите, я вам сразу обозначу грабли. То, что вы делаете — это CRUD-генератор. Идея в целом хороша, но есть одно но.
    При разработке подобных штук вы неизбежно упретесь в дихотомию "гибкость-порог вхождения". Иными словами, если вы сделаете простой инструмент, который позволяет делать подобные CRUD-интерфейсы быстро, то он будет негибким. Что это значит? Это значит что на сравнительно простых задачах (накидать CRUD для простенькой БД) ваши юзеры будут пищать от восторга. Однако, когда к ним придет капризный клиент и понадобится сделать что-либо выходящее за задуманную вами функциональность (ну например клиент захочет концептуально другой дизайн, или там… я не знаю… скрывать кнопочку "сохранить" в зависимости от хитрых параметров валидации, или в конце концов рисовать поней в ячейке датагридов) — то тут-то ваши юзеры повесятся, потому что возможности глубокой кастомизации вы, скорее всего, не предусмотрите. Это нормально — вы же делаете простую вещь, так?
    Но это не трагедия. Трагедия вот в чем. Если вы решите-таки сделать возможность глубокой кастомизации, сядете и тщательно нарисуете архитектуру, предусмотрев кучу extension points для таргет-юзера, то порог входа в вашу библиотеку неуклонно поползет вверх и для более-менее массового использования вы будете вынуждены писать тонны документации. И далеко не факт что ваши таргет-юзеры захотят в ней копаться.
    Но в целом — удач вам и успехов :)


    1. pnovikov
      17.10.2017 00:07
      +1

      (чуть подумав) Проблема так же известна как "шаг влево/шаг вправо — расстрел". Самое узкое место у пользователей будет возникать, когда надо изменить ну вот совсем маленькую фиговинку. Хоть ту же поняшу добавить. Очень, знаете ли, обидно из-за фичи вроде как на 2 минуты работы отключать стандартный интерфейс и пилить все на angular-е. Ну то есть я говорю о кейсах, на которые разработчик смотрит и думает — о, да тут работы должно быть на полчаса максимум. А когда пытается это по факту сделать — то получается целая эпопея.


    1. Fesor
      17.10.2017 00:34

      По поводу проблем CRUD ориентированных интерфейсов можно вот эту статейку попробовать почитать: https://cqrs.wordpress.com/documents/task-based-ui/


      Если кто похожее чего порекомендует, буду признателен.


      1. pnovikov
        17.10.2017 17:16

        Я такое и реализовал у себя в Lattice. Демок пока нет, лично могу показать реализацию.


    1. navar Автор
      17.10.2017 00:54

      Это очень актуальный вопрос. Такая проблема ярко выражена в Django Admin, почему я и отказался от его использования для ряда задач. Разрабатывая фреймворк, я старался минимизировать её влияние.


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


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


      1. Генерация всего интерфейса автоматически.
      2. Генерация отдельных частей интерфейса автоматически.
      3. Полная ручная кастомизация интерфейса или его части.

      Поэтому ситуация, когда среди автоматически сгенерированных элементов нужно разместить на форме нестандартные элементы или элементы в произвольном порядке, решается давольно просто. Пример такой кастомизации: https://github.com/astoniocom/astonio-shop/tree/master/src/app/flow-record-window


      Если в Django для кастомизации используется подход "внедряемся и меняем", то у меня используется подход "собираем заново из кирпичиков", причем кирпичиком может быть как самописная, так и встроенная часть. Также для решения проблемы с порогом входа применяются принципы разработки приложений на Angular. Как итог, даже при полной кастомизации интерфейса технически внедрить свои элементы — задача с низким порогом входа (хотя от него, безусловно, никуда не деться).


      1. pnovikov
        17.10.2017 17:13

        Я говорю как раз о моменте, когда "видит око, да код неймет". Т.е. ты видишь какую-то фичу, которая визуально должно реализовываться просто, но по факту осознаешь, что надо собирать все из кирпичиков.
        Я сделал что-то подобное на C#, да и дошел примерно до того же. Все закончилось, к слову, написанием собственного движка клиентского шаблонирования. Как говорится, we need to go deeper. И у меня как раз получилось так, что ради сложной кастомизации надо не собирать решение из кирпичиков, а разбирать готовое, потом собирать обратно. Как подход — рекомендую. Think about it :)


        1. coh
          17.10.2017 17:35
          +1

          Дело, мы сделали так же. Дошли до дизайнера интерфейсов.
          Easy easy, real talk :)


          1. pnovikov
            17.10.2017 17:40

            Братюни.
            К слову, люди из JS-сообщества, как показала практика, вообще не признают проблематику, поднятую в статье


  1. Tonkonozhenko
    17.10.2017 00:51

    Для подобных целей использовал activeadmin. Из коробки более-менее нормально кастомизируется с помощью DSL.


    1. zharikovpro
      17.10.2017 12:37

      Более того, если нужно — он кастомизируется полностью. Т.к. можно частично или полностью переопределять controller и view, это все что нужно.


  1. Cobolorum
    17.10.2017 08:27

    1 If DB = Oracle THEN Oracle + APEX
    2 If DB like ODBC & OS=Windovoz THEN MS Access
    3 If DB like JDBC & OS != Windovoz THEN OpenOffice Base


  1. alexs0ff
    17.10.2017 08:32

    1) Посмотрел код, сразу удивился, что используется прямое подключение к базе из клиента. Как мне его перебросить в интернет?
    2) Почему angularJS? так исходники используют на Angular 4.


  1. ermolalex
    17.10.2017 11:33

    Если все таки из готового, то может рассмотреть ODOO?


  1. zharikovpro
    17.10.2017 12:31

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

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


  1. zharikovpro
    17.10.2017 12:33

    Кажется, вы пишете десктопную open source версию PhpMaker :).


  1. kos_d
    17.10.2017 13:49

    Вот бы ещё дизайна добавить, а то выглядит как привет из 90-х


  1. Timur_n
    17.10.2017 16:35

    Добрый день друзья, немного некорректно выглядит сравнение с встроенной «админкой» django и вот почему: django гибкий инструмент, использование django-admin ограничивает Вас в рамках самой админки, поэтому в "«боевых» проектах обычно не используется, пишется свое, этот подход рекомендуют и для начинающих программистов. В любом случае Ваш проект интересный, удачи)