Архитектура Kolesa.kz: от микросервисов к монолиту

Abduazim Yergeshev
Kolesa Group
Published in
7 min readDec 14, 2022

Я Абдуазим Ергешев, backend-разработчик Kolesa Group, работаю в команде монетизации продукта Kolesa.kz. Это сайт бесплатных объявлений о продаже и покупке автомобилей в Казахстане. Занимаюсь доработкой существующих и разработкой новых сервисов для обеспечения работы платных услуг.

В своей статье я расскажу про:

• Архитектуру на первых этапах
• Микросервисы
• Сложности
• Плавный переход в монолит

Абдуазим Ергешев

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

Примеры платных услуг

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

Архитектура на первых этапах

1. API. Основной сервис, наш монолит. Вокруг него строится всё взаимодействие. Мы вынесли туда всю логику с объявлениями: поиск, модерация, валидация параметров, работа с данными. Раньше цены на платные услуги у нас были зашиты в код и хранились в обычном массиве.

2. Архитектура

У нас есть ещё один слой в виде сервиса site. Он брал объявление и цены на платные услуги из API, и отдавал в нужном нам формате. Для десктоп-версии это веб-страничка, для мобильной — json с подтянутыми параметрами. На этих двух сервисах у нас всё работало.

Но со временем логика усложнялась и разрасталась. Весь код находился в одном месте — монолите, и его стало сложно поддерживать. Решили разбивать код на мелкие сервисы — микросервисы.

Микросервисы

С начала 2015 года мы стали потихоньку выносить некоторый функционал монолита в микросервисы. Так у нас появился:

- платёжный шлюз;
- сервис личного счёта;
- сервис калькуляции цен на платные услуги.

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

Давайте рассмотрим, что он из себя представляет.

Цену мы запрашивали по идентификатору объявления. Микросервис:

1) забирал объявление из Mongo;
2) вытаскивал правила из базы MYSQL;
3) подсчитывал всё это с правилами цен;

4) В конце выдавал нам цену на платные услуги;

5. В итоге получилась такая схема. Долгое время это всё работало отлично.

Но наступил 2020 год. Изменения в мире повлияли на наши продуктовые решения. У бизнеса появились много гипотез, которые нужно проверить. К нам стали прилетать задачки, такие как:

- дифференциация цены в зависимости от типа пользователя;
- дифференциация цены в зависимости от лимитов;
- скидка на первую покупку платной услуги.

Сложности

Но тут мы столкнулись с проблемами. Изначально мы построили архитектуру так, что APS получал все необходимую информацию из параметров объявления. Теперь нам нужно было знать и информацию о пользователе. Задача была важная и сделать её нужно было как можно скорее.

Мы начали покрывать недостаток данных, пробрасывая информацию о пользователе из API в APS. Наш микросервис стал зависим от API. И это стало проблемой.

При проектировании микросервисов мы часто сталкиваемся с такими принципами как loose coupling и high cohesion. Трудность заключалась в, прежде всего, сильной связности.

Сильная связность

Объясню, что эти принципы из себя представляют:

а) High cohesion

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

High cohesion

б) Loose coupling

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

Loose coupling

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

И мы замечаем, что с каждой новой задачей API обрастает связями c APS. Даже некоторую логику стали писать в монолит для расчёта цен. Границы сервиса стали размываться.

Это ведёт к увеличению срока разработки. Потому что к нам часто приходят задачи, которые нужно решать в комплексе. Мы делаем доработки сначала в APS, затем в API. После чего их надо подружить между собой.

И у нас возник такой вопрос «А нужны ли нам такие сложности»? Для чего нам микросервисы:

1. Когда нужно гибко масштабироваться

В APS нам нет необходимости в масштабировании. Операция по калькуляции цен не очень ресурсоёмкая. Для расчёта цен мы спамим запросами в API. Поэтому если нагрузка увеличится, скорее API приляжет, чем APS.

2. Когда нужно использовать независимый стек технологий

Но для калькуляции цен нам не нужны специальные инструменты, библиотеки. Задачу можно решить на любом языке программирования.

Следовательно, от использования таких микросервисов существенных преимуществ мы не получаем. Их сильно перевешивают минусы:

1. Дополнительная точка отказа
Каждый элемент в системе — это, по сути, дополнительная точка отказа.

2. Сложность отладки
Когда логика у нас размыта по нескольким сервисам, это добавляет сложность в отладке.

3. Таймауты
В ситуациях, когда просто моргнула сеть или отвалился микросервис, возникают таймауты.

4. Задержки
Бонусом мы получаем задержки на межсервисные запросы.

Плавный переход в монолит

Мы задумались: а может лучше в монолит? Но не в простой, а модульный. Думаю, многие применяют практику, когда нужно разбить большой монолит на разные зоны ответственности. И наложить какой-то контекст на них.

Преимущества модульного монолита

1. Получаем изоляцию, как в микросервисах

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

2. Проще проводить границы модулей и менять

В нашем случае это идеальный вариант. Так нам проще становится всё рефакторить в зависимости от бизнес-требований. Экономим наши ресурсы: усилия и время.

3. Упрощается отладка кода

Нам не нужно разворачивать у себя кучу микросервисов и отлаживать в комплексе. А если в команду придёт новый человек? Он потратит много времени на подготовку и начало работы.

4. Возможность переиспользования инфраструктурного кода

Это кэши, мониторинг, логирование. Которые уже настроены и обкатаны в проекте.

Обеспечение перехода в монолит

Так как в APS ходил не один сервис, нам нужно было сделать так, чтобы переход никак не повлиял на работоспособность остальных сервисов. Тем более на сервисы, которые являются общими для всех продуктовых команд. Для этого рассмотрим, как устроено взаимодействие сервисов с APS на примере платёжного шлюза.

Когда-то APS был общим для всех продуктов. То есть у нас был один микросервис, который считал цены на платные услуги для всех продуктов Kolesa Group. Поэтому мы напилили client на PHP и вынесли его в отдельный репозиторий APS-client, чтобы его могли переиспользовать другие сервисы. После форка APS в каждом продукте появилась своя версия APS, и мы продолжили использовать APS-client потому, что структура запросов и ответов были одинаковыми у всех. Но сервис платёжного шлюза так и остался у нас общим.

Учитывая всё это, мы подогнали наш API под такую схему, чтобы писать меньше логики в общих сервисах и не повлиять на другие продукты. Затем переопределили хосты из APS в API во всех сервисах.

В итоге у нас получается вот такая вот схема:

Какую пользу мы от этого получили

1. Сокращение сроков на разработку

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

2. Упрощение процесса разработки

Имея всё под рукой, нам стало легче структурировать код.

3. Сокращение сетевых запросов

Раньше мы делали три запроса для получения цен: получали объявление, отдавали параметры в APS, а тот, в свою очередь, делал запрос для того, чтобы проверить превысил лимит пользователь или нет. Что сумме, в среднем, занимало 430 ms.

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

Заключение

Я не противник микросервисов. У нас много микросервисов, которые работают отлично. Просто, в зависимости от бизнес-задач, полезными могут быть разные решения. APS, как микросервис, нас полностью устраивал больше 5 лет, но со временем мы пришли к пониманию, что будет лучше перейти на монолит. Сейчас APS на такой стадии, что правильнее ему находиться в одном репозитории с API.

--

--