Как мы реализовали Low-code на микросервисах

Привет Хабр!

Меня зовут Алексей Пушкарёв, я — архитектор продуктовых решений компании ELMA. Наша команда разрабатывает одноименную Low-code платформу.  В этой статье я расскажу, почему мы выбрали микросервисную архитектуру для Low-code системы вместо классической монолитной, которой до этого занимались много лет. Поясню, почему использовали именно такие технологии и решения, с какими недостатками сами столкнулись. Поговорим, как такая архитектура сказалась на Low-code разработчиках.

Для кого эта статья? Для архитекторов, аналитиков, внедренцев и тимлидов и всех тех, кому интересна тема Low-code. Мне кажется, что в публичном пространстве мало информации об архитектуре таких решений и применяемых технологиях. Для многих они до сих пор остаются черным ящиком, что нередко приводит к обманутым ожиданиям, и в целом недоверию к Low-code как к технологии. Хочу показать, что находится под капотом у таких систем на примере платформы, которую сами разрабатываем.

bc318673657d1dff6a83a2a10f40a324.png

Пару слов о Low‑code и No‑code

Итак, наша команда разрабатывает Low-code платформу ELMA365. Давайте разберемся, что это значит. Термин Low-code разные вендоры трактуют достаточно по-разному, поэтому напишу, что подразумеваем под этим мы. Low-code — это набор инструментов для разработки приложений и среда для исполнения этих приложений. Исходя из названия понятно, что цель таких систем — снизить объем кода, который потребуется для разработки.

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

Что представляют собой инструменты Low-code разработки?

Инструменты разработки в Low-code — это конструкторы, чаще всего различные редакторы схем, WYSIWYG-редакторы, или просто наборы таблиц для настройки бизнес-логики работы приложения. Примеры — редактор схемы бизнес-процесса и WYSIWYG-редактор для создания пользовательских форм. Для того чтобы заработали схемы, настройки и другие артефакты, которые мы создали в визуальных инструментах платформы, используется движок. С помощью него осуществляется интерпретация этих моделей. Если быть точнее, есть несколько движков для разных моделей. Движки также включены в платформу и составляют среду исполнения для разработанных приложений.

Структура компонентов, из которых состоит решение на Low-code платформе ELMA365

Структура компонентов, из которых состоит решение на Low-code платформе ELMA365

Какие приложения можно разрабатывать с помощью Low-code платформ? Практически любые. Но, как правило, у каждой платформы есть своя ниша. Например, наша платформа, специализируется на разработке корпоративных приложений, нацеленных на совместную работу пользователей и автоматизацию бизнес-процессов компании. Эта сфера разработки, как правило, связана с постоянно меняющимися требованиями и необходимостью постоянной доработки приложений.

Почему мы решили делать Low-code на микросервисной архитектуре?

Мы начинали разработку системы как облачное SaaS-решение для малого и среднего бизнеса в 2018 году. Своих распределенных дата-центров у нас нет, поэтому мы арендуем вычислительные мощности. Исходя из этого мы очень внимательно относимся к использованию вычислительных ресурсов. Наши потребности:

  1. гибкая система управления нагрузкой — управление лимитами нагрузки для компаний,

  2. гибкая автоматическая масштабируемость системы под нагрузкой, создаваемой клиентами.

К тому времени у нас уже был успешный опыт разработки монолитной Low-code системы (наш прошлый продукт) и первая мысль была — адаптировать под работу в SaaS. Однако выстроить эффективное управление нагрузкой на разные экземпляры системы для разных компаний в нашей архитектуре у нас не получилось. Все сводилось к развертыванию отдельных виртуальных машин под каждого клиента, что приводило к нескольким проблемам:

  1. высокая себестоимость решения,

  2. сложность автоматизации управления нагрузкой.

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

Промышленным стандартом для такой задачи можно назвать Kubernetes. Этот сервис позволяет гибко настраивать масштабирование серверных ресурсов и предоставляется облачными провайдерами. На нем мы свой выбор и остановили.

Вторым следствием SaaS-решения явилась мультитенантная архитектура и разделяемая инфраструктура. Мультитенантная архитектура очень распространена среди таких решений (SaaS), можно сказать Best practice. Ее суть сводится к тому, что в одном экземпляре приложения работает одновременно несколько компаний (клиентов) и используются общие вычислительные ресурсы. Изоляция компаний друг от друга осуществляется внутри самого приложения и за счет разделения баз данных. А исполнение скриптов, разработанных пользователями, происходит внутри песочницы, без прямого доступа к ресурсам сервера, только через API самой системы. Это позволяет максимально эффективно использовать облачные ресурсы и не допускать несанкционированного доступа к чужим данным.

Кроме того, разделение на сервисы позволило нам организовать работу нескольких команд и разделение на несколько репозиториев. Каждая команда работает в своем изолированном пространстве, соблюдая контракт для работы с другими сервисами системы. Также существует набор внутренних стандартов, библиотек и принципов для всего процесса разработки. Замечу, что разделение на сервисы и соответствующая инфраструктура позволяет легче встраивать в систему свои High-code сервисы в тех случаях, когда Low-code инструментов становится недостаточно.

Мультитенантная и микросервисная структура ELMA365

Мультитенантная и микросервисная структура ELMA365

Спустя пару лет у нас появилась также поставка On-Premises для развертывания внутри компаний заказчиков, т.к. использовать SaaS-решение оказались готовы далеко не все. Принципиальных изменений в архитектуру для On-Premises решений мы не вносили. Допускаю, что для части клиентов такая архитектура избыточно сложна, особенно когда речь не идет о высоких нагрузках. Однако реализовать поддержку двух видов архитектур в одном приложении практически невозможно — получились бы разные приложения. Решение, которое мы нашли — вариант поставки On-Premises с упрощенным вариантом администрирования и упаковкой кластера в один контейнер.

Архитектура системы глазами Low-code разработчика

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

В ELMA365 мы скомбинировали микросервисную архитектуру платформы и привычную для Low-code разработчиков единую среду разработки и исполнения решений.

Если смотреть на систему глазами Low-code разработчика, то процесс разработки будет выглядеть как построение монолитного решения. При этом внутреннее разделение на сервисы и необходимость связывать их между собой будет от него скрыто. Почему так?

Во-первых, специфика задач, которая решается Low-code инструментами — это достаточно высокоуровневая разработка бизнес-логики, затрагивающая разные аспекты работы компании. Часто требуется работа с данными из разных областей в одном месте.

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

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

Технологический стек

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

Однако мы не стали ударяться «в полную самостоятельность» и писать каждый сервис «кто во что горазд». Для компании содержать множество центров компетенций по каждому языку программирования и фреймворку очень накладно. Это доступно только компаниям-гигантам с тысячами разработчиков. Поэтому мы взяли за внутренний стандарт разработку на языке Go.

Этот язык достаточно простой, производительный и очень хорошо подходит для микросервисной разработки. Не буду подробно останавливаться на Go, я думаю многие с ним знакомы. Также есть несколько сервисов, разработанных на C#, например, сервис для автоматической генерации документов и работы с ними в формате Microsoft office. C# был выбран, потому что, во-первых, у нас уже есть такие компетенции (предыдущие наши продукты ELMA ¾ были разработаны как раз на нем), а во-вторых, нам подходит набор библиотек, который есть под .Net для работы с документами.

При разделении системы на отдельные микросервисы мы стремимся придерживаться принципа изоляции вычислений при выполнении бизнес-транзакций. Поэтому в одном сервисе может быть сосредоточено достаточно много логики.

На текущий момент в системе несколько десятков сервисов. Если требуется выделить новый — внимательно анализируем требования. Без серьезного обоснования необходимости мы новые микросервисы не выделяем. Каждый сервис работает со своим набором данных. Доступ к данным другого сервиса осуществляется через сам сервис, а не прямым вызовом в базу данных.

Совсем упрощать сервисы мы не стали — в какой-то момент, на наш взгляд, сложность интеграции сервисов между собой уже начинает превышать сложность самой логики его работы. Конечно,   простой сервис можно относительно безболезненно написать заново, вместо того, чтобы в нем глубоко разбираться и дорабатывать. Но просто так заменить один сервис на другой не получится: важно сохранить совместимость и реализовать миграцию. Иначе могут пострадать Low-code решения, созданные на платформе. Поэтому мы выбрали нечто среднее между мелкими простыми сервисами и монолитным решением, а к переписыванию сервисов целиком прибегаем достаточно редко.

Общение между микросервисами мы построили на gRPC вызовах для синхронного взаимодействия.  Для асинхронных вызовов и событийного взаимодействия используем шину RabbitMQ. Для работы с фронтовой частью и внешнего API — HTTP-интерфейс. Также он используется для исполнения пользовательских скриптов, создаваемых в рамках Low-code решений. Передача данных между сервисами осуществляется, как правило, в параметрах в самом сообщении. Для передачи значительных объемов данных может применяться и внешний кэш, для него мы используем Redis.

Ниже на схеме отражены главные сервисы используемые в системе. Оговорюсь, что некоторые нюансы опущены, чтобы сохранить читаемость.

Схема взаимодействия микросервисов

Схема взаимодействия микросервисов

Фронтовая часть — web, десктоп и мобилка

Кратко расскажу про фронтовую часть нашей системы. Мы предоставляем 3 интерфейса для пользователей: web, десктопное и мобильное приложения. При этом все они построены на web-технологиях, поэтому мобильное и десктопное приложение — это по сути тонкие обертки. А в основе — web-приложение. В десктопе мы используем Electron, а в мобильном — Cordova.

Разрабатывать нативное мобильное приложение мы не стали, т.к. в этом случае для Low-code платформы придется обеспечить работу интерфейсов, созданных Low-code разработчиками, на всех платформах. А реализовать отдельный интерпретатор для мобильного интерфейса — задача крайне сложная и вряд ли получится лучше, чем существующие html-интерпретаторы.

Само web-приложение построено на Angular и представляет собой single page application c динамической подгрузкой данных с сервера. Это позволяет нам строить достаточно отзывчивый интерфейс, несмотря на некоторую избыточность и неоптимальность, порождаемую самой спецификой Low-code подхода. В создаваемых Low-code решениях интерфейс разрабатывается с помощью визуального конструктора, а разработчики в большинстве задач не готовы тратить время на тонкую оптимизацию.

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

Где хранятся данные?

Основным хранилищем пользовательских данных выступает база данных PostgreSQL. В ней находятся структурированные данные, которые использует сама платформа. В ней же находятся данные объектов, которые разрабатывает Low-code разработчик в своих приложениях. Замечу, что Low-code разработчик не работает напрямую с хранилищем, а использует либо интерфейс системы (где настраивает структуру данных, возможности фильтрации, формы приложений), либо внутренний SDK в сценариях. Работу со структурой данных, индексами и построение запросов платформа берет на себя.

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

Структура таблиц в базе у нас тоже не совсем классическая: данные пользовательских объектов хранятся в JSONB-полях в виде документов, рядом также в JSONB-поле лежат настройки доступа к элементу. Связи между объектами хранятся в отдельной таблице связей, реализуя, таким образом, связь «многие ко многим». Такой подход позволяет нам с одной стороны реализовывать Schemaless-хранение структурированных данных, проще реализовывать операции изменения структуры данных через Low-code инструменты и хранить вложенные структуры. С другой стороны, мы можем использовать реляционные механизмы соединения таблиц и построение выборок данных, например, для отчетности. Для ускорения выборок мы используем индексацию полей документов хранящихся в JSONB-полях. В системных таблицах, используемых самой платформой, мы уже применяем и классическую реляционную структуру полей. Хотя где-то используем и JSONB-поля с вложенными структурами. Такой подход к хранению данных можно назвать гибридным. Замечу, что в работе с данными мы используем принцип мягкого удаления: из интерфейса системы или Low-code инструментов жестко ничего удалить нельзя. Такой подход для бизнес-пользователей более предпочтителен, поскольку снижает риск потери данных в случае ошибок пользователей или Low-code разработчиков и позволяет сохранять целостность данных.

Пример структуры хранения пользовательских данных

Пример структуры хранения пользовательских данных

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

Для хранения файлов используем S3 совместимое хранилище, которое может считаться современным стандартом, в нашем случае это система MinIO.

Плюсы и минусы микросервисной архитектуры

Главные плюсы в реализации микросервисной (сервисной) архитектуры:

  • Удобно вести параллельную разработку платформы несколькими командами и поддерживать достаточно высокий темп.

  • Гибкое и автоматическое масштабирование системы, эффективно используются ресурсы кластера в SaaS-решении.

  • Возможно построить отказоустойчивое решение как в условиях размещения в облаке, так и в частном кластере.

  • Сохраняется достаточная простота и понятность разработки решений для Low-code разработчиков.

К минусам я бы отнес:

  • В случае использования поставки On-Premises к администратору на месте будут предъявляться существенные требования по настройке и администрированию инфраструктуры, то есть определенный уровень знаний настроек Kubernetes. В помощь ему мы постарались все подробно описать в справке. Также отдельного внимания и существенных серверных ресурсов будут требовать системы логирования и мониторинга.

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

  • Избыточное потребление ресурсов на накладные расходы в случае поставки On-Premises, когда не требуется гибкое масштабирование инфраструктуры.

Спасибо, что прочитали этот лонгрид до конца! Статья получилась достаточно объемной. Надеюсь, что вы узнали для себя что-то новое о Low-code системах и наш опыт окажется для вас полезным.

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

Как вы считаете, выигрывают ли Low-code системы, используя микросервисную архитектуру вместо классического монолита?

И что в целом думаете про идею использования Low-code платформ как фреймворка для разработки?

© Habrahabr.ru