Знакомьтесь: Object Manager​, или Создаем генератор веб-интерфейсов для объектов

Aleksandr Kostarev
Xsolla Tech Blog
Published in
7 min readAug 30, 2021

Современные веб-приложения можно разрабатывать на базе собственной или облачной инфраструктуры. Это касается и отдельных ее частей — бэкенда или базы данных. Можно развернуть свой собственный сервер или использовать готовую платформу, например, Backend as a Service (BaaS). Если вы выбираете первый вариант, бэкенд нужно создавать с нуля и управлять всей инфраструктурой самостоятельно. Плюсы этого подхода — большая гибкость и широкие возможности для кастомизации/настройки. Минусами будут высокие затраты на разработку и длительное время выхода на рынок. При выборе второго варианта вы можете использовать готовые блоки и инструменты генерации кода. Так вы получаете более быстрый процесс разработки и сокращение времени выхода продукта на рынок. Минусы этого подхода — меньшая гибкость и шаблонная архитектура.

Облачные базы данных также предоставляются по модели PaaS (платформа как услуга). Их часто называют DBaaS, сокращенно от Database-as-a-Service (база данных как услуга). Такая платформа чаще всего используется для хранения, управления и извлечения данных. По сути, DBaaS — система управления базами данных, установленная поверх облачной инфраструктуры (IaaS). Это позволяет обращаться к базе данных напрямую и масштабировать ее под нагрузкой «на лету», что довольно сложно при использовании традиционных инстансов баз данных.

При выборе бэкенда для нашего нового продукта — собственной CRM-системы — мы ориентировались в первую очередь на ускорение процесса разработки и, соответственно, снижение ее стоимости. Бизнес-контекст для нашей системы представляет из себя различного вида объекты (Games, Companies, Contacts, Products, etc) и связи между ними. Для каждого объекта нужен свой CRUD. Кажется, что при едином стиле оформления страниц создание приложения должно превратиться в скучную работу по копированию и адаптации практически одинаковых CRUD-форм объектов. Заниматься скучной работой нам не хотелось, поэтому мы начали искать альтернативные варианты и задались вопросом — что если научить систему генерировать формы объектов автоматически, на основе заранее подготовленной спецификации?

Выбор и кастомизация бэкенда для генератора объектов

Первым делом мы поискали что-то подобное в уже готовых библиотеках. Внимание к себе привлек проект Machinable — его возможности оказались наиболее близки к тому, что нам было нужно:

  • Machinable умеет создавать простые объекты, которые хранит в jsonb, и эндпоинты для CRUD, а также генерировать спецификацию на базе OpenAPI. При этом спецификация и объекты хранятся отдельно. По сути это тот же самый BaaS.
  • У проекта есть веб-админка, которая хоть и не обладает всеми нужными нам возможностями, но умеет создавать разные типы объектов, а также автоматически генерирует и отображает документацию по ним.
  • В проекте реализована система управления учетными данными. Базовая аутентификация использует связку логин/пароль.
  • В Machinable реализована система рейт-лимитов и партиционирование базы данных (PostgreSQL). Cистема рейт-лимитов позволяет контролировать и ограничивать количество запросов в сутки по определенному проекту и пользователю. Партиционирование в PostgreSQL — это раз­би­е­ние таб­лиц, содер­жа­щих боль­шое коли­че­ство запи­сей, на логи­че­ские части для повышения скорости и удобства выполнения запросов. В данной системе оно работает “из коробки”, предлагая партиционирование через наследование таблиц.

Определившись с базой для нашего проекта, мы сделали форк и приступили к его кастомизации под наши задачи. Несколько важных доработок было сделано нашими коллегами из команды Xsolla Funding. Например, аутентификация была переписана на Xsolla Login, что значительно расширило ее возможности. Изначально можно было зайти в систему только через связку логин/пароль, после доработок стали доступны другие способы авторизации, в частности, через Google. Также в проекте изначально не была реализована работа со связями между объектами, ребята из Funding Team исправили этот недостаток. Еще одной существенной доработкой стало изменение способа партиционирования базы данных на декларативное. Это увеличило скорость работы с базой, а также помогло настроить правильный ETL-процесс в наш корпоративный Data Warehouse.

Автогенерация веб-интерфейсов из объектов

Кроме изменений в бэкенде нужен был и сам генератор веб-интерфейсов. Его мы назвали Object Manager и начали создавать с нуля. За основу был выбран ReactJS, так как это стандарт разработки фронтенда в компании. Сам генератор представляет из себя монорепозиторий, который собирается в два отдельных npm-пакета. Эти пакеты могут подключаться в виде внешних библиотек в любой другой репозиторий. Далее, через стандартные роуты на React в нужных местах можно вызывать нужные формы. Мы указываем в параметрах объект и говорим генератору, какую форму CRUD мы хотим получить. Далее он автоматически собирает эту форму на основе спецификации.

Например, спецификация для объекта Company может выглядеть так:

В ней мы описываем основные параметры объекта, а также список полей с их типами и параметрами в разделе properties. В searchableField указываем поле для компонента поиска на списке, в required — перечень обязательных полей для заполнения на формах создания/редактирования. В массивах filterable и sortable — список полей, по которым список будет фильтроваться и сортироваться. В editable, creatable, listViewed, detailViewed — те поля, которые будут отображаться на карточках редактирования, создания, списка и детализации объекта соответственно.

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

Дальше описываем роуты для CRUD-страниц объекта Company:

Теперь можно создать отдельные компоненты для CRUD-страниц. Компонент для списка компаний будет выглядеть так:

Остальные компоненты будут такими же простыми.

Создание:

Редактирование:

Детализация:

После того, как все готово, запускаем приложение и смотрим результат:

Аналогичным образом добавляется и любой другой объект, например, Games.

Кнопка фильтров не отображается на списке, так как не указан соответствующий параметр в схеме объекта Games. Точно так же мы можем не указывать и не отображать на форме списка кнопку сортировки и поиск.

Карточка игры:

Форма редактирования:

Таким образом, для любых изменений полей на формах CRUD нам достаточно просто обновить схему, менять код на формах компонентов при этом не требуется — нужный нам порядок и список полей отобразится автоматически. Унификация помогает избежать лишних ошибок при переписывании нескольких компонентов, если мы пишем CRUD-формы вручную, и в разы сокращает время на их создание/редактирование. Так, если раньше мы оценивали добавление форм для нового объекта в 3–4 сторипоинта, то теперь нам для этого достаточно 1–2. Основная задача в итоге была достигнута — вместо скучного “формошлепства” мы теперь генерируем страницы объектов автоматически, а свободное время используем для более творческих задач.

Что дальше

Однако у медали, как известно, есть две стороны. За унификацию и остальные плюсы автоматически генерируемых форм приходится платить. Например, их однообразным внешним видом и довольно высокой сложностью кастомизации. Использование одинаковых компонентов для полей различных типов также усложняет их переиспользование в некоторых случаях. Например, на форме фильтров у нас используются те же самые поля, что и на формах создания/редактирования. И если, например, на форме создания в поле Name достаточно просто положить в него строку, то при добавлении фильтрации по этому полю поведение должно быть другим — нам нужен либо выпадающий список, либо поиск по введенным буквам. Решение подобных задач значительно усложняет разработку на начальном этапе (иногда тоже в разы), хотя и дает ощутимые преимущества в долгосрочной перспективе.

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

В качестве благодарности хотелось бы отметить моих коллег: Григория Валеева и Дмитрия Машковцева — людей, стоявших у самых истоков и внесших огромный вклад в создание и развитие нашего продукта. А также Евгения Секерина — системного архитектора проекта, за неоценимую помощь в создании этой статьи и тестового стенда для нее.

--

--