О клиенте и продукте
ВипСервис — крупнейший российский поставщик тревел-услуг для корпоративных клиентов. Холдинг входит в список 200 крупнейших частных компаний России по версии Forbes. Внутри компании целая экосистема цифровых продуктов, которые решают разные задачи от предиктивной аналитики до агрегации отелей. Work Solutions с 2020 года развивает систему бронирования тревел-услуг для корпоративного бизнеса Портбилет ТМС, благодаря которой каждый год путешествуют 13 миллионов пассажиров.
Краткие сведения
Количество часов в проекте: 665ч
Стек: PHP 8.0.28, Symfony 6.0, mysql 8.0.29
Формат работы: 1 senior-backend разработчик и 1 middle-backend разработчик
Ответственные со стороны заказчика: 1 руководитель проекта, 1 руководитель цифровых проектов
Задача
Перевести глобальных клиентов на платформу Портбилет TMC. До этого момента системой пользовалась только компания Вип Корпорейт Тревел (ВКТ), которая обслуживает локальных клиентов, а за международное направление отвечает Вип Бизнес Тревел (ВБТ). В структуре холдинга это два независимых подразделения. Добавление ВБТ означало, что внутри системы нужно вести отдельный учет по всем заказам, проводить интеграции с внешними API поставщиков и не только.
Проблема
Исторически Портбилет TMC рассчитан под обслуживание главной компании ВКТ. Если посмотреть сверху, система имеет древовидную структуру, где есть базовая компания и дочерние ответвления.
Работоспособность всего приложения зависит от основной компании. Например, у главной компании есть идентификатор «1». Этот идентификатор использовался повсеместно в коде проекта как константа, в том числе в клиентских сервисах. То есть, чтобы получить данные по компании, нужно в коде вызвать единицу.
Фактически, главная компания — это поставщик для всех дочерних компаний. И если у клиентов не было своих индивидуальных настроек, то они использовали глобальные настройки ВКТ.
Текущая архитектура не предусматривает добавление новой глобальной компании, поэтому ее появление само собой предполагает глобальные изменения в приложении.
Решение
Путей решения три: копия проекта, глобальный рефакторинг и мультитенантность.
Сравним их достоинства и недостатки.
Копия проекта. Первое, что приходит на ум. Такой подход требует меньше всего усилий на старте, но недостатки обнаружатся быстро.
Во-первых, одновременно сопровождать код в двух системах сложнее, чем в одной. Особенно, когда новую функциональность нужно добавить в оба сервиса, но при этом один из них сильно ушел вперед. Чтобы доработка функционировала в отстающей системе, ее придется подтягивать до состояния первой, в том числе и решать проблемы ну уровне различий в структуре базы данных, которые неизбежно возникнут.
Во-вторых, дубли систем не подразумевают сквозного использования, например, для роли администратора, чтобы он мог бесшовно переключаться между глобальными компаниями.
![Изображение цитаты](https://worksolutions.ru/uploads/g_Cy_HG_0_Hbr_MXW_Yf9r_UN_5_Tgw_Jc_Lxpfz7_P7_MPNT_Zsdi_a573dc358a.png)
Больше восьми лет развивали сервис предоставления банковской гарантии для Госгарант. Однажды один из клиентов захотел приобрести систему как on-prem решение. Из-за ограничений по срокам пришлось сделать копию. Спустя год этих дублей стало пять. Их интегрировали в банки федерального уровня. Каждый требовал персональных доработок, из-за чего проблемы накапливались снежным комом. Разумеется, здесь мы решили не идти путем дублирования, так как сегодня нужна одна копия — а завтра десять.
Глобальный рефакторинг проекта. Решение из разряда «все переписать». Полностью перебрать кодовую базу проекта, чтобы избавиться от статических элементов в коде, а также добавить новый интерфейс с материнскими компаниями, которые работают автономно. Решение кажется верным, но слишком затратным.
Мультитенантность. Решение, которое предусматривает единый интерфейс и при этом позволяет не зависеть от статической «единички». Кажется, то, что нужно, чтобы доработать функциональность на возможность добавления ещё одной сервисной компании.
Что такое мультитенантность
Мультитенантность или мультиарендность — это особенность архитектуры приложения, которая позволяет управлять несколькими организациями (тенантами) одновременно. Для каждой организации выделяется собственная база данных, и система понимает, в какой момент с какой БД ей работать.
Основное правило мультитенантности — структура всех баз данных должна быть идентична. Все таблицы и поля должны быть одинаковыми. Тогда если мы делаем доработку только для одного тенанта, миграции всегда применяются на обе базы.
Как это контролируется? У каждого тенанта есть отдельная таблица, в которой перечислен список функций. У каждой записи внутри этой таблицы есть колонка с активностью. Если в колонке стоит true, то функционал используется на этом тенанте. Если false — не используется. Для разработчиков это сильно упрощает процесс работы: сразу понятно, какие функции глобальные, а какие применяются на конкретном проекте.
Препятствия
Несмотря на удобство функционала мультитенантности, не обошлось без проблем:
1. Распределение всех пользователей по БД. Теперь, когда в системе две базы данных, нужно понимать, какие пользователи к каким тенантам имеют доступ. Также нужно где-то хранить общие настройки и атрибуты. Для этого создали системную третью БД. Она содержит в себе таблицу с глобальными идентификаторами GUID и работает как кроссервис. В ней же обозначены разрешения, какой пользователь с каким тенантом может работать.
2. Разделение задач по тенантам. На проекте используем брокер Gearman, который еще шутливо называем «Дядя Гирман». Он умеет работать с фоновыми задачами и хранит внутри состояние о текущем подключении к БД. Дядя Гирман понимает, какие задачи брать, какие ставить помеченными и т.д. Нужно было придумать, как работать с двумя тенантами, чтобы задачи не пересекались. Для решения использовали консольные утилиты — когда доступ к сайту не через HTTP, а через консоль. У каждой команды появился новый аргумент с тенантом. Он сообщает о том, на каком тенанте должен выполняться код. Причем это касается и кода, и миграций.
3. Разделение доменов. Изначально приложение работало на одном домене, но так как каждый тенант — это отдельная компания со своими данными и юридическим лицом, необходимо подключить каждый тенант на отдельный домен.
Поменять запись в бд при переключении между тенантами в рамках одного домена несложно, но когда пользователь переключается на второй домен, у него свои cookies, настройки браузера и т.д. Тут нужен функционал автоматической авторизации пользователя на втором тенанте. Для этого сделали микросервис аутентификации, который также используем в качестве централизованной БД с данными о пользователях.
Запуск проекта
Для релиза подготовили чистую базу данных с одной компанией под новый проект. Написали скрипт, который делает копию БД, убирает ненужное и создает отдельную компанию. Команда запускается по кнопке на Jenkins и добавляет копию с текущей БД уже с нужными настройками. Поэтому запуск в продакшн прошел плавно без неожиданностей.
Что это дало заказчику
Заказчик получил возможность распределения данных по нескольким компаниям. Помимо этого, теперь в проект можно добавлять новые тенанты за довольно короткое время — разработчик со знанием системы сможет «поднять» новый тенант за неделю. Например, сейчас планируется подключение третьей компании в проект, и этот процесс уже не вызывает опасений.
![Изображение цитаты](https://worksolutions.ru/uploads/Anastasiya_223d8ae6b9.jpg)
На первый взгляд, добавление нового клиента не кажется чем-то столь сложным, но система создавалась другой командой и такая возможность в первоначальный замысел не входила. Команда Work Solutions повела себя профессионально — на своем опыте показала ограниченность решения с дублированием кодовой базы, при этом не стала настаивать на ее полном переписывании. Мультитенантность позволила запуститься за два месяца и получить нужный запас прочности.
Заключение
Мультитенантность позволила добавить в систему новые возможности без глобальной переработки. Если бы не это решение, приложение пришлось бы полностью рефакторить.
Создание копии приложения для нас стояло на последнем месте из-за проблем с дальнейшей поддержкой подобных сервисов. Но тут стоит учитывать ещё один момент — в мультитенантности весь код находится на одной серверной инфраструктуре. То есть один код обрабатывает одновременно несколько тенантов. И если бы нам нужно было полностью изолировать систему от остальных, то пришлось бы делать копию.