Все говорили о микросервисах. Гибкость. Масштабируемость. Независимые команды. Звучало как мечта. Многие компании бросились распиливать свои монолиты. Разработка действительно ускорилась. Отдельные компоненты стало проще обновлять и разворачивать.
А потом сервисам понадобилось взаимодействовать. И мечта превратилась в сложную, многомерную головоломку.
Простой вызов функции внутри монолита стал сетевым запросом. Надежность упала. Задержки выросли. Отладка превратилась в квест по распределенным логам. Внезапно оказалось, что взаимодействие сервисов — это не второстепенная деталь. Это ядро всей архитектуры. Ошибка здесь стоит дорого. Она приводит к хрупким, медленным, небезопасным и дорогим в поддержке системам. Переход на микросервисы — это не просто технический рефакторинг. Это полная смена парадигмы мышления для инженеров, тестировщиков и менеджеров.
Проблема №1 — Эффект домино: каскадные сбои и хрупкость системы
Одинсервис недоступен. Сервис, который его вызывает, ждет ответа. У него заканчиваются потоки или соединения. Он тоже становится недоступен. Эффект домино, или каскадный сбой, обрушивает всю систему. Это самая частая и опасная болезнь микросервисных архитектур, способная превратить незначительный сбой в полномасштабный отказ.
Решение А: Паттерн «Прерыватель цепи» (Circuit Breaker)
Это автоматический выключатель между сервисами. Он не просто повторяет запросы, он управляет состоянием соединения. У него три состояния. Closed: Обычный режим, все запросы проходят. Open: Если количество ошибок за период превышает порог, прерыватель «размыкается». Все последующие вызовы немедленно завершаются ошибкой на стороне клиента, без попытки уйти в сеть. Это дает упавшему сервису время на восстановление и, что важнее, защищает сам сервис‑клиент от исчерпания ресурсов. Half‑Open: Через заданный тайм‑аут прерыватель переходит в это состояние. Он пропускает один‑единственный тестовый запрос. Если он успешен, прерыватель замыкается (Closed). Если нет — снова размыкается (Open). Этот механизм самовосстановления критически важен.Решение Б: Паттерн «Изолирующий отсек» (Bulkhead)
Представьте себе отсеки в корпусе корабля. Пробоина в одном отсеке не затапливает все судно. Этот паттерн работает так же. Ресурсы сервиса‑клиента (например, пул соединений или пул потоков) делятся на группы. Каждая группа выделяется для взаимодействия с конкретным удаленным сервисом. Если сервис B тормозит и занимает все потоки, это повлияет только на его собственный «отсек». Вызовы к другим сервисам (C, D) будут использовать свои пулы и работать как обычно. Это предотвращает деградацию всего сервиса из‑за проблем с одним из его соседей. На практике это реализуется созданием отдельных пулов потоков (как в библиотеке Hystrix) или семафоров для каждого типа вызовов.
#include <mutex>
#include <semaphore>
#include <thread>
class Bulkhead {
public:
explicit Bulkhead(int maxConcurrent) : semaphore(maxConcurrent) {}
bool execute(auto function) {
if (!semaphore.try_acquire()) {
return false;
}
std::thread t([this, function]() {
try {
function();
} catch(...) {
}
semaphore.release();
});
t.detach();
return true;
}
private:
std::counting_semaphore<> semaphore;
};
Решение В: Асинхронное взаимодействие
Это фундаментальное решение, которое предотвращает проблему, а не борется с ее последствиями. Вместо прямого вызова, который ждет ответа, сервис‑отправитель публикует сообщение в очередь (RabbitMQ, Kafka) и немедленно продолжает свою работу. Сервис‑получатель заберет сообщение из очереди, когда сможет. Если он в данный момент недоступен, сообщение просто подождет в брокере. Это полностью разрывает временную связь между сервисами. Сбой одного никак не влияет на работоспособность другого в моменте. Система становится на порядок надежнее и эластичнее.
Проблема №2 — Сага о согласованности данных
Нужно выполнить бизнес‑операцию, затрагивающую несколько сервисов: заказ, оплата, склад. В монолите это решалось одной ACID‑транзакцией. В мире микросервисов распределенные транзакции — это боль и страдания. Пытаться их реализовать «в лоб» почти всегда приводит к катастрофе.
Решение А: Паттерн «Сага» (Оркестрация)
Появляется отдельный сервис‑координатор — оркестратор. Он знает весь бизнес‑процесс, представляя его в виде конечного автомата. Он посылает команды сервисам‑исполнителям один за другим. «Списать деньги». Ждет ответа (события). Получил «Деньги списаны». Посылает команду «Резервировать товар». Если на каком‑то шаге приходит событие об ошибке, оркестратор начинает процесс компенсации. Он посылает команды для отмены предыдущих успешных шагов («Вернуть деньги»). Вся логика централизована, ее легко читать, отлаживать и изменять. Но оркестратор становится критически важным компонентом, и его отказ остановит все управляемые им процессы.Решение Б: Паттерн «Сага» (Хореография)
Здесь нет центрального координатора. Сервисы общаются друг с другом через события. Orders публикует событие OrderCreated. Сервис Payments слушает это событие, списывает деньги и публикует PaymentProcessed. Сервис Warehouse слушает PaymentProcessed. Это похоже на танец, где каждый знает свою партию. Для отката также используются события. Payments публикует PaymentFailed, и Orders, услышав его, отменяет заказ. Это решение более децентрализовано и отказоустойчиво. Но логика процесса размазана по системе, что усложняет понимание и отладку. Понять, на каком этапе находится конкретный заказ, становится очень сложно.Решение В: Паттерн «Transactional Outbox»
Это критически важный технический паттерн для надежной реализации асинхронных саг. Как гарантировать, что изменение в базе и отправка сообщения в брокер произойдут вместе? Ответ: никак, если это разные системы. Паттерн решает это так: сервис пишет бизнес‑данные и само событие в свою же базу данных в рамках одной локальной ACID‑транзакции. Событие пишется в специальную таблицу outbox. Отдельный легковесный процесс (relay) постоянно опрашивает эту таблицу, забирает неотправленные события, публикует их в Kafka или RabbitMQ и помечает как отправленные. Это гарантирует, что событие не потеряется, даже если сервис упадет сразу после коммита транзакции, но до отправки.
Проблема №3 — Деградация производительности и «тысяча порезов»
Каждый сетевой вызов добавляет миллисекунды на установку соединения, TLS-рукопожатие, сериализацию. Цепочка из десяти таких вызовов добавляет сотни миллисекунд. Пользователь не будет ждать.
Решение А: Эффективные бинарные протоколы (gRPC)
Данные гоняются в Protobuf. Это бинарный, жестко заданный формат. Машина его "читает" в разы быстрее, чем развесистый JSON. Для общения сервисов между собой, где вы сами себе и клиент, и сервер, gRPC — это просто здравый смысл, если уперлись в скорость.Решение Б: Материализованные представления (CQRS)
Если сервис Orders часто нуждается в имени клиента из сервиса Users, постоянные запросы по сети — это медленно. Вместо этого можно применить подход CQRS (Command Query Responsibility Segregation). Orders может подписаться на события UserUpdated и хранить у себя актуальную, денормализованную копию нужных данных (например, в своей же базе или в Redis). Чтение этих данных становится мгновенным локальным вызовом. Скорость чтения возрастает многократно, но ценой является конечная согласованность и усложнение логики записи.Решение В: Агрегация на стороне API Gateway или BFF
Вместо того чтобы клиентское приложение (например, мобильное) делало три запроса к разным сервисам для отображения одного экрана, оно делает один запрос к специальному фасаду. Это может быть API Gateway или, что еще лучше, Backend for Frontend (BFF). BFF — это отдельный сервис, который является бэкендом для конкретного фронтенда. Он знает, какие данные нужны этому клиенту, параллельно опрашивает внутренние сервисы, агрегирует и трансформирует ответы в удобный для клиента формат.
Проблема №4 — Тиски разработчика: локальная среда и тестирование
Разработчику нужно внести правки в сервис, который зависит от 15 других. Как запустить и протестировать это? Поднять 15 сервисов, Kafka, PostgreSQL, Redis — нереально. Продуктивность падает до нуля, а цикл обратной связи растягивается на часы.
Решение А: Контрактное тестирование (Consumer-Driven Contracts)
Тестируется не живая интеграция, а соблюдение контракта. Сервис-потребитель пишет тест, который определяет, какие данные он ожидает от сервиса-провайдера. Этот тест генерирует файл-контракт (например, с помощью инструмента Pact). Этот файл передается провайдеру. Провайдер в своем CI/CD-пайплайне автоматически проверяет, что его текущая версия API не нарушает контракты всех своих потребителей. Это позволяет тестировать сервисы в полной изоляции.Решение Б: Виртуализация сервисов и заглушки (Mocks/Stubs)
Это могут быть простые моки в коде или полноценные сетевые заглушки-стабы. Они притворяются реальными сервисами и отвечают так, как вы им скажете. Инструменты вроде WireMock позволяют в пару строк настроить, чтобы заглушка отвечала ошибкой 503 или думала по две секунды.Решение В: Инструменты для разработки в Kubernetes (Telepresence, Gefyra)
Есть инструменты, которые делают почти невозможное. Тот же Telepresence. Он позволяет запустить один сервис у себя, и он волшебным образом становится частью удаленного кластера. Сетевые запросы из облака прилетают к вам, а ваш код может обращаться к базам и сервисам в этом облаке, будто он там живет.
Проблема №5 — Распределенная дилемма данных: Joins vs. Duplication
В реляционном мире мы просто делали JOIN. В мире микросервисов прямой запрос к базе данных другого сервиса — абсолютное табу, так как это создает жесточайшую связность. Как получить связанные данные?
Решение А: API Composition
Сервис-агрегатор (или API Gateway/BFF) делает несколько вызовов к разным сервисам и «джойнит» данные у себя в памяти перед тем, как отдать результат клиенту. Например, получает заказ из Order Service, а затем по userId из заказа получает данные пользователя из User Service. Просто, но может быть медленно и неэффективно.Решение Б: Event-Carried State Transfer
Это более продвинутая версия репликации данных. Вместо того чтобы событие содержало только ID сущности, оно содержит все ключевые данные, которые могут понадобиться потребителям. Например, событие OrderCreated будет содержать не только ID товаров, но и их названия и цены на момент заказа. Это избавляет сервис-потребитель от необходимости делать последующий синхронный вызов, чтобы обогатить данные.Решение В: Общая база данных (антипаттерн)
Иногда команды выбирают этот путь для кажущейся простоты. Несколько сервисов используют одну и ту же схему базы данных. Это создает скрытые, хрупкие зависимости на самом глубоком уровне стека. Изменение таблицы одним сервисом может сломать десять других. Это убивает главные преимущества микросервисов — автономность и независимое развертывание. Этого следует избегать почти всегда.
Проблема №6 – Системная слепота: наблюдаемость и отладка
Проблема возникла где-то в цепочке из семи сервисов. Логи разбросаны, тайминги неизвестны. Найти причину — все равно что искать иголку в стоге сена. Без правильных инструментов вы слепы.
Решение А: Распределенная трассировка (Distributed Tracing)
Обязательно к внедрению. На входе в систему (в API Gateway) генерируется уникальный ID трассировки (Correlation ID). Этот ID, вместе с ID текущего шага (Span ID), пробрасывается через все вызовы (в HTTP-заголовках или метаданных сообщений). Библиотеки (например, OpenTelemetry) автоматически собирают эти данные и отправляют их в систему вроде Jaeger или Zipkin, которая строит наглядный граф вызовов, показывая тайминги каждого шага.Решение Б: Централизованное логирование со структурой
Логи всех сервисов должны отправляться в единую систему (ELK Stack, Loki, Splunk). Критически важно, чтобы логи были структурированными (например, в формате JSON), а не просто текстом. Это позволяет делать мощные запросы: «покажи все логи с уровнем 'error' для сервиса 'payment‑service', где 'trace_id' равен 'xyz'».Решение В: Метрики бизнес‑уровня
Помимо технических метрик (CPU, память), нужно отслеживать бизнес‑метрики, проходящие через сервисы. Например, «количество созданных заказов в минуту», «процент ошибок при оплате», «время от регистрации до первой покупки». Часто падение бизнес‑метрики — первый и самый важный сигнал о скрытой технической проблеме.
Проблема №7 — Операционный хаос: обнаружение и конфигурация
У вас 100 сервисов. Как сервис A узнает IP-адрес сервиса B? Где сервис C хранит пароль от базы данных? Управлять этим вручную в файлах — путь к катастрофе.
Решение А: Реестр сервисов (Service Discovery)
Каждый экземпляр сервиса при старте регистрируется в реестре (Consul, etcd, Eureka), сообщая свой адрес и порт. Когда сервису A нужно вызвать B, он сначала спрашивает у реестра: "дай мне список здоровых экземпляров сервиса B". Затем он выбирает один из них и делает вызов.Решение Б: Централизованный сервер конфигурации
Все конфигурации (строки подключения, feature flags, тайм-ауты) хранятся в одном месте (Spring Cloud Config, HashiCorp Consul KV). Сервисы при старте запрашивают свою конфигурацию с этого сервера и могут подписываться на ее изменения. Это позволяет менять настройки на лету без перезапуска сотен экземпляров сервисов.Решение В: Service Mesh (сетка сервисов)
Такие инструменты, как Istio или Linkerd, берут на себя всю «черную работу» по взаимодействию. Они внедряют прокси‑контейнер (sidecar) рядом с каждым сервисом. Этот прокси прозрачно для приложения управляет обнаружением, балансировкой нагрузки, шифрованием (mTLS), Circuit Breaking, повторами, трассировкой. Разработчики могут сосредоточиться на бизнес‑логике, а не на инфраструктурных проблемах. Это мощное, но сложное решение.
Проблема №8 — Эволюция API и версионирование
Сервис A обновился, его API изменился. Все сервисы, которые от него зависели, сломались. Поддерживать обратную совместимость — сложная, но необходимая задача для сохранения независимости команд.
Решение А: Версионирование в URI или заголовках
Новая версия API, ломающая совместимость, доступна по новому адресу (/api/v2/users) или требует специального заголовка (Accept: application/vnd.company.v2+json). Простой и понятный способ управлять изменениями. Старая версия (v1) поддерживается до тех пор, пока все клиенты не мигрируют.Решение Б: Расширяемые форматы данных (Tolerant Reader)
Используйте форматы, которые терпимы к изменениям, тот же Protobuf. Идея проста: клиент должен игнорировать любые поля в ответе, которые он не понимает. Добавили новое поле? Старый клиент его просто не заметит. Это позволяет развивать API, не плодя версии.Решение В: Паттерн «Адаптер» (Anti‑Corruption Layer)
Когда трогать старых клиентов страшно или невозможно (привет, старое мобильное приложение на полке у CEO), ставят сервис-адаптер. Это прослойка-переводчик. Она принимает старый формат запроса, переделывает его в новый, а ответ переводит обратно. Это стратегический ход, чтобы изолировать новую, чистую часть системы от наследия прошлого.
Проблема №9 — Дыры в безопасности: доверие и авторизация
В монолите все вызовы были внутри одного процесса. В микросервисах они идут по сети. Кто угодно в сети может попытаться вызвать ваш сервис. Принцип «Zero Trust» (нулевое доверие) — не опция, а необходимость.
Решение А: Service Mesh для взаимного TLS (mTLS)
Сетка сервисов автоматически обеспечивает шифрование всего трафика между сервисами и выдает каждому сервису сертификат. При установке соединения сервисы проверяют сертификаты друг друга, чтобы установить доверие (mutual TLS). Это гарантирует, что с вашим сервисом говорит именно тот, за кого себя выдает, и что трафик зашифрован.Решение Б: API Gateway как точка входа
API Gateway — это ваш охранник на входе. Он проверяет подлинность внешних запросов (например, по JWT‑токену пользователя), прежде чем пропустить их во внутреннюю сеть. Он выполняет аутентификацию (кто ты?) и грубую авторизацию (имеешь ли ты право заходить?).Решение В: Токены доступа для сервисов (OAuth2 Client Credentials)
Для детальной авторизации между сервисами используются токены. Сервис A получает от сервера авторизации собственный токен (JWT) с правами (scopes), например orders:write. При вызове сервиса B он передает этот токен. Сервис B проверяет токен, чтобы убедиться, что у A есть права на выполнение данной операции.
Практические рекомендации
Correlation ID — это не опция, это закон. Без сквозного ID трассировки вы слепы.
Делайте каждый изменяющий эндпоинт идемпотентным. Сеть повторяет запросы. Будьте к этому готовы.
Владение данными — свято. У каждого фрагмента данных должен быть один и только один сервис-владелец.
Мониторьте p99 задержки, а не среднее. Среднее значение скрывает боль ваших самых недовольных пользователей.
Ломайте систему сами в тестовом окружении. Внедряйте хаос-инжиниринг. Найдите слабые места до того, как их найдут ваши пользователи.
Начинайте с простого. Не нужно сразу внедрять Service Mesh и Kafka. Начните с REST, Circuit Breaker и Service Discovery. Усложняйте по мере необходимости.
Думайте о разработчиках. Обеспечьте простой способ запуска и тестирования сервиса локально. Счастливый и продуктивный разработчик — залог успеха проекта.
Успех с микросервисами — это не про слепое следование моде. Это про глубокое понимание компромиссов. Каждый день вы балансируете на канате между производительностью, надежностью, стоимостью и скоростью разработки. Задача инженера — не найти один «правильный» ответ, его не существует. Задача — выбрать наименее болезненный компромисс для конкретной задачи здесь и сейчас. И быть готовым его пересмотреть завтра, когда изменятся требования или появятся новые инструменты.
Комментарии (78)
olku
02.08.2025 00:06Спасибо за статью. Некоторые уточнения.
MSA это прежде всего про deployability: независимые команды, отдельные компоненты. Ответ на Conway's law. Остальные качества имеются и в других архитектурах.
CQRS не обязательно про данные, это еще и способ распилить мега-сервисы.
OpenTelemetry это скорее стек - набор сервисов, агентов и библиотек. Похоже, вы не имели с ним практики. Tempo, например, показывает трассировку и граф прямо в Grafana, нет нужды в Jaeger и Zipkin.
sobeskiller
02.08.2025 00:06Проблема прежде всего архитектурная. Там где микросервисы создают проблемы - там они скорее всего и не нужны. Правильная архитектура - модульная, с распределенным выполнением слабовзаимодействующих отдельных модулей, там где это приносит выгоды. Да даже монолит целиком может выполняться распределённо, в режиме разных ролей.
rukhi7
02.08.2025 00:06Проблема №1, Проблема №2, ... Проблема №N
а теперь представить сколько должно быть кода чтобы решать (не решить, а хотя бы пытаться) все эти проблемы. Заниматься бизнес логикой будет некогда.
Интересно что хотел сказать автор? Доказать утверждение:
И мечта превратилась в сложную, многомерную головоломку.
cstrike
02.08.2025 00:06Представьте что вы имеете машину: Kia Picanto. Она вас довозит с точки А в точку Б и все бы ничего, но вот ваша семья растет и вас уже не двое а пятеро. Приходится делать две ходки чтобы всех перевезти. А теперь у вас еще и дача появилась, нужно туда инструменты перевезти, а оттуда 3 мешка яблок забрать. Да и дорога на дачу - ямы да ухабы. Решение? Меняете вы свою малютку на Dodge Ram. Все проблемы решились на раз. Большая, вместительная, мощная, проходимая! Но, содержание стало дороже, бензина жрет больше, в городе не развернуться, а найти стоянку вообще отдельная беда.
Это я к чему? Не существует серебряных пуль. Решая одну проблему, мы обретаем другую.
rukhi7
02.08.2025 00:06только некоторые монолиты бывают покрупнее целой кучи микросервисов, вы не видели из какого объема исходников компилируется какой-нибудь не самый навороченный андроид?
Потом вроде как монолит или микросервисы не покупают, а строят.
А проблемы действительно по одной не ходят.
PerroSalchicha
Есть одно хорошее правило, как решать описанную в вашей статье проблему: если в вашей архитектуре появилось множество вызывающих друг друга микросервисов, значит, вам пришла пора переходить на монолит. Вы получите массу преимуществ - существенно, в разы увеличите производительность приложения, в разы уменьшите стоимость эксплуатации инфраструктуры, повысите надёжность, упростите отладку, тестирование. Ваши юнит-тесты начнут приносить пользу, а не тупо отрабатывать KPI по проценту покрытия кода, как это обычно в микросервисах. И заплатите вы за это, ну, может быть, несколько большим временем деплоя. Т.е. куча плюсом и крохотный минус.
GeorgeII
И меньшей способностью к масштабированию
PerroSalchicha
Горизонтально монолит масштабируется так же, как и микросервисы, путём увеличения числа экземпляров. Вертикально нет, но потребность в вертикальном масштабировании с лихвой компенсируется куда более высокой пропускной способностью.
Kanut
Так никто не говорит что монолит в принципе нельзя масштабировать.Но если у вас микросервисы, то можно масштабировать только ту часть сервиса, на которую растёт нагрузка. А вот нужно ли это конкретному продукту это уже отдельный вопрос.
LeoKudrik
А в чём профит? В том, что вы не несёте на сервера всю кодовую базу, а всего лишь один сервис? Всего то? Тут всё очень зависит от реализации монолита.
UPD Думаете это стоит того, что бы удорожать разработку в несколько раз?
Kanut
Во первых разработка не обязательно дорожает в несколько раз. Откуда у вас такие цифры?
А во вторых вы например экономите на железе или там плате за облака.
LeoKudrik
Для меня порядки цены разработки очевидны - это минимум x3. Более того у меня огромный опыт работы и с тем, и с тем. Когда вы вместо простого вызова функции/метода начинаете городить всё вышеописанное в статье, то вам:
Нужна очень квалифицированная и добросовестная команда; скорее всего их несколько, ибо гонять сообщения/вызовы api самим себе по пять раз за запрос вот вообще смысла нет;
Удорожается отладка - вам нужно делать тестовые стеки из набора связанных микросервисов, потому что их десятки и они связаны; тестировать один сервис отдельно вы можете в самом начале его разработки, ибо потом он обрастает зависимостями и вам нужен будет стек и да, юнит-тесты уже не канают или это моковые монстры; в монолите - запустили инстанс, прогнали функциональные/интеграционные тесты - всё!
Не дай бог вы в качестве дефолтной изоляции для микросервисов используете докер - тут вообще всё плохо ибо это все еще теперь надо билдить и пайплайны будут идти часами. Цена ошибки и внедрения новой фичи в таких случаях очень велика, как и кол-во ресурсов на инфру (оно огромно) ну и конечно эмоции разработчиков! Не забудьте, что билдить надо будет так же и для того, что бы просто запустить интегр. тесты на стеке - а это то время, когда вы не можете работать с исходниками
Если вы не хотите репутационных проблем - вам ОБЯЗАТЕЛЬНО нужно будет делать всё, что описано в этой статье, а это далеко не для новичков;
Все апишки, контракты взаимодействия между сервисами нужно будет описывать и держать их в актуальном состоянии; мультиверсионность - та еще боль! ... я могу продолжать дальше, но работа ждёт)
Я вам так скажу. Раньше, где-то до появления докера (до середины 10ых), такие продукты были только у компаний с огромными нагрузками, изоляциями типо jail (FreeBSD, первая) и LXC (linux, позднее) и инфы про такие системы было мало, ибо это были их конкурентные преимущества, а в отрасли было чёткое понимание того, что когда ты выходиш из общего адресного пространства, да и ещё имеешь нагрузки выше среднего, ты получаешь возможность масштабировать и терминировать эту нагрузку, но тебе нужно будет обеспечивать целостность разделяемых ресурсов между разными окружениями, а это как в многопоточном приложении - самые трудноуловимые ошибки и даже сложнее - в многопоточке у тебя общее адресное пространство и там ты можешь использовать ipc примитивы а в микросервисной - всё гораздо жёсче. Так что как-то так, коллега)
Вы просто пройдитесь по плюсам микросервисов и вам будет всё понятно. Это Архитектурная преждевременная оптимизация по Кнуту.
Kanut
А для меня это очень зависит от ситуации. И в некоторых ситуациях микросервисы даже дешевле будут.
Возьмите не квалифицированную и не добросовестную команду и попробуйте написать и поддерживать монолит на миллион строк кода.
Вы про моки ничего не слышали?
Вообще смешной аргумент. Ну не используйте докер если видите в этом проблему.
Нет, совсем не обязательно делать всё, что описано в этой статье.
Это и в монолите надо делать.
Я вам так скажу: микросервисы существуют гораздо дольше. Только их далеко не всегда называли микросервисами :)
LeoKudrik
Это математически невозможно. Вы простой вызов метода заменяете сетевым взаимодействием.
Для монолита нужны просто программисты. Для микросервисов нужна гораздо более квалифицированная команда, для меня это очевидно. И кол-во строк кода будет в микросервисах гораздо больше, нежели в монолите. Вам нужно будет всё это сетевое взаимодействие выразить в коде.
Еще как слышал, про моки и стабы, котрые нужно писать - это деньги. И в случае юнитов вы тестируете только код, но не то кол-во логики, которое вынесено на уровень инфраструктуры.
Я сказал "если" - а их, как правило и используют, не понимая, что это несет для компании. Прочитайте внимательно, что я написал.
Только в случае внешних взаимодействий. Всё.
Так я об этом и написал! И это всегда была боль ради распределения нагрузки.
Kanut
Зато постоянно происходит :)
Даже близко нет. И этот комментарий намекает мне что вы не особо разбираетесь в теме.
Ага. Я посмотрю на вас когда у вас пара сотен "просто программистов" начнут работать над общей кодовой базой в несколько миллионов строк кода :)
А в монолите их не нужно писать? У вас там только интеграционные тесты?
Нет. Особой боли там нет. И делается это не только ради распределения нагрузки.
LeoKudrik
Тут где то написали - если у вас бизнес масштаба Вайлдберриз - то микросервисы необходимы. Но в 95% случаев - это не так. Предположу, что у вас это тоже не так (хотя могу ошибаться) и все ваши сервисы только лишь приносят боль. Не вам - компании!
Это ваши фантазии.
Kanut
Это не значит что во всех остальных случаях они не нужны.
У нас это по разному. И в отдельных проектах мы используем микросервисы(или точнее распределённую архитектуру} потому что монолит создаёт там гораздо больше проблем.
LeoKudrik
Перечислите.
Раздельный деплой? Деплойте весь монолит - это возможно.
Работа с репой нескольких команд? Организуйте исходники, используя DDD, домены и ограниченные контексты - и вы будете пересекаться именно в тех случаях, когда зависимости необходимы - это сигнал для "события предметной области". И да - монорепа - это благо.
Вам нужно масштабировать нагрузки? В правильном монолите это решается поднятием уровня изоляции с потока/корутины до процесса/контенера.
Вот и все плюсы микросервисов решены в монолите.
Монолит вам создаёт кучу проблем, потому что вы не умеете всё вышеперечисленное.
Kanut
"Возможно использовать монолит" не означает "удобнее/дешевле использовать монолит".
Я точно так же могу вам сказать что микросервисы создают вам кучу проблем потому что вы не умеете их правильно использовать.
Ну и как бы расскажите мне как правильно использовать монолит когда:
-Часть функционала пишется не нами, а самими клиентами или нашими партнёрами или даже просто отдаётся на аутсорс.
Из-за сторонних зависимостей приходится использовать абсолютно разные тех-стеки.
Работать разные части кода должны на разном железе. Причём так что одна часть на винде, другая на соседней машине на линуксе, третья интегрируется в какой-нибудь TestStand, а четвёртая запускается в эмбеддед-окружении на каких-нибудь датчиках или производственном железе.
LeoKudrik
Библиотеки не пробовали? :D Так написан весь линукс)
Остальное - частные случаи, которые редко встречаются в разработке и в данном контексте просто не применимы.
Еще раз - микросервисы дороже математически - не спорьте!!!
Kanut
Конечно пробовали. А вы не пробовали интегрировать в монолит библиотеки из кучи разных ЯП, фрэймворков, версий и так далее?
У вас есть статистика? Можете ссылочку дать?
Ещё раз: это просто голословное утверждение. Более того я вам привёл конкретный пример когда это не так.
LeoKudrik
Так похоже вы сами себе и сделали подобный винегрет) а потом за уши притягиваете микросервисы/распределенные архитектуры. Так и живите с ним "счастливо". Я говорю о среднестатистическом веб-приложении, ибо в "коробке" о микросервисах даже и говорить не приходится)
Kanut
Мы себе ничего не сделали. Это требования от клиентов на реальных производственных линиях.
А весь мир ограничивается среднестатистическими веб-приложениями?
LeoKudrik
Представьте себе, в большинстве случаев - да!
Так вы микросервисы на производственных линиях используете? Без каких либо нагрузок? :big beautiful facepalm:
Kanut
Тяжко вам там в параллельной вселенной...
Нагрузки это не единственная причина для использования распределённой архитектуры.
sobeskiller
А вот с этим вот соглашусь с товарищем. Если так сложилось что какие-то модули написаны на чёрти-чём или тащят неведомые зависимости, то проще отколупать это и пусть живёт оно своей жизнью. Лишь бы контракт интерфейсов соблюдало.
Что до сетевого оверхеда - столь тесно связанные функции однозначно должны быть макисмально бинарно приближены друг к другу. В остальных же случаях, отколупать такой независимый модуль как минимум не возбраняется (но надо ли?).
LeoKudrik
То, что должно работать в таком окружении - пусть в нем и работает. В остальном - монолит)
Kanut
А теперь переходим к следующему уровню сложности: в теории 80-90% всего решения могут получить такую зависимость. Ну или точнее от проекта к проекту это реально происходит.
Не проще тогда построить инфраструктуру на микросервисах, написать "стандартные" сервисы для всего что надо и потом только по необходимости менять отдельные сервисы на проприетарные решения в отдельных проектах?
LeoKudrik
Во-первых - не фантазируйте! Такие зависимости появляются крайне редко.
Во-вторых - такие зависимости как-раз таки можно вынести в отдельные окружения, заставить общаться по апи и жить с этим.
В-третих - только нагрузки - объективная причина появления таких архитектур. Веб и Нетфликс всему виной! Все остальные вопросы можно так или иначе решить без распределения.
На этом заканчиваю. Спасибо!
Kanut
Так я не фантазирую. Я вам описываю реальную ситуацию.
Ну так что делать если в теории практически 99% кода могут словить такую зависимость? Что куда выносить? Как организовать архитектура чтобы в каждом новом проекте можно было переиспользовать как можно больше базового кода?
Это вы сейчас сами придумали?
Kanut
В монолите можно просто менять отдельные его части во время рантайма? Как насчёт вышеупомянутого масштабирования?
Не надо делать микросервисы просто ради того чтобы всё было стильно, модно и молодёжно. Микросервисы нужны когда требуются частые обновления и масштабируемость. Или например когда у вас над проектом работают большие разделённые команды с кучей разработчиков. Или когда по какой-то причине надо использовать разные технологии. Или например когда у вас данные по какой-то причине должны быть строго разделены(например из-за юридических требований). И так далее и тому подобное.
Raspy
Можно конечно менять отдельные части. В чём проблема то?
Kanut
То есть грубо говоря ваш монолитный сервер запущен и работает. И вы не останавливая работу меняете отдельные части?
То есть я понимаю что в принципе это решаемо. Через какие-нибудь плагины или динамическую загрузку библиотек. Но на мой взгляд это всё тоже не особо просто, удобно и самое главное стабильно в рантайме.
PerroSalchicha
Почти. Я обновляю его целиком, но да, не останавливая работу. Этого достаточно, замена именно по частям не есть самоцель, цель - обеспечить бесперебойное обслуживание клиентов, монолит в этом плане ничуть не уступает микросервисам. Плагины (и вообще какие-либо доработки архитектуры) не требуются, всё это прозрачно обеспечивает инфраструктура облака. Вы поднимаете новый инстанс в новом слоте, существующие запросы дорабатывают на старом, новые запускаются на новом, старый инстанс после завершения запросов отключается.
Kanut
И это гораздо сложнее и больше геморроя чем обновления в микросервисной архитектуре. Особенно если вам необходимо относительно часто делать относительно небольшие изменения.
Ещё раз: всё имеет свои плюсы и свои минусы. Микросервисы это не панацея и не серебряная пуля. Но в определённых ситуациях они лучше монолита.
PerroSalchicha
Вообще нет, ни капли не сложнее, просто больше машинного времени на сборку/развёртывание, но это делает глупая железяка, а не люди.
Я с вами абсолютно согласен, единственное, что я утверждаю, что этих ситуаций, где микросервисы лучше монолита, намного меньше, чем многим кажется. По сути дела, опросник по внедрению микросервисов должен выглядеть как-то так:
Ваш электронный бизнес размером с Вайлдбериз?
Нет? Значит, вам микросервисы пока ещё не нужны.
Kanut
И например тестировать надо больше. И больше последствий при проблемах.
У меня такой статистики нет. И по моему личному опыту большинство людей вообще не особо думают когда принимают такие решения. Кто-то из них просто принципиально за и поэтому пихает микросервисы везде где надо и где не надо. А кто-то принципиально против и выбирает монолит даже если это создаёт кучу проблем.
У нас вообще не электронный бизнес. Мы продаём решения для автоматизированных конвейрных линий для индустрии. Но всё равно вынуждены каждый второй проект делать на микросервисах.
PerroSalchicha
Это не так на самом деле. Изменения в монолите стоят по тестированию/последствиям примерно столько же, сколько и в микросервисах. Исключения есть в гипотетических примерах, когда как вы и предположили, если вдруг кто-то затянул новую версию библиотеки, да эта библиотека вдруг используется везде, да в новой версии поломалась совместимость со старой, вот тогда опаньки. Сколько таких библиотек в природе существует? Newtonsoft json есть, но они совместимость не ломали. Зато есть у монолита и в тестируемости бонус: там легко заменить бестолковые юнит-тесты на эффективные интеграционные. И будет ваше тестирование тоже успешно делать железяка.
Я согласен с вами, хайповая мотивация у нас вообще преобладает над осознанной.
Ну вы-то, вероятно, предлагаете архитектуру, соответствующую той, что уже есть у клиента, а не пишете ему что-то с нуля на голые серверы в чистом поле.
Kanut
Ну нет же. Зависимость сильнее. Единичные изменения могут затрагивать большее количество кода. Банально те самые общие библиотеки.
Полно.
Но это не значит что не может быть каких-то "побочек". И если у вас развлекательная платформа, то вы можете забить на тесты. В какой-нибудь медицинской технике или там финтехе придётся каждый раз всё проверять.
Архитектура у нас чисто своя. Проблема в зависимостях и библиотеках от клиентов.
PerroSalchicha
Ну так она сильнее по способу компоновки, а не по числу внутренних связей и зависимых компонент. И если вы вносите изменения в функционал какого-то сервиса в монолите, это касается только тех компонент, которые этот сервис вызывают, ровно так же, как и в микросервисах.
Да, как и в случае микросервисов, я в mission critical софте тоже их все буду загонять в интеграционные тесты и проверять, при изменении всего лишь одного из них.
Kanut
Нет. И я выше уже привёл пример с общими библиотеками.
Но в монолите вам нужно не только интеграционные тесты делать, но и проверять отдельно каждый кусок кода, который имеет зависимость на библиотеку, которая поменялась. В отличии от микросервисов. То есть как ни крути, в в монолите будет больше тестов.
PerroSalchicha
Да. Я не ошибусь, если скажу, что 99.99% времени разработки вы не меняете общие библиотеки. А 0.01% можно пренебречь.
Мне не надо его проверять, мне достаточно убедиться, что интеграционные тесты работают корректно, значит, и нижележащий код работает корректно. Тем более что, в отличии от микросервисов, я прекрасно знаю все куски кода, где та библиотека используется, и могу это контролировать.
Kanut
Ошибётесь.
Это где-то час в год если я всё правильно прикинул.
То есть у вас есть только интеграционные тесты? Но при этом такие, которые покрывают абсолютно всё? Не расскажите как вы такого добились?
Какой размер кодовой базы у вашего монолита? Ну что вы знаете все куски кода?
PerroSalchicha
Где-то так, да. А как часто вы там новые общие библиотеки в проекте меняете?
Затрудняюсь сказать, как вы к этому выводу пришли.
Я и не знаю. Мне их IDE в монорепе за секунду покажет, и напишет, кто каким куском занимался.
Kanut
Регулярно. Например присылает добрый клиент новую версию своей библиотеки. А там зависимость от условного NewtonsoftJson конкретной версии. И приходится везде эту версию использовать.
Ну вы же написали что вам не надо проверять отдельные куски кода.
Ну вот кто-то поменял версию того самого условного NewtonsoftJson. Вам IDE покажет все куски кода, которые из-за этого могут начать неправильно работать?
Ну то есть давайте представим что там баг и где-то какое-то поле при сериализации/десериализации получит неправильное значение в определённых ситуациях. Как вам IDE покажет все места, которые надо проверить?
rpc1
Это зависит от различных факторов и процессов. Как правило, для деплоя монолита в крупной компании используются релизы, а не автоматический деплой после после каждого изменения. по этой причине, чтобы обновить монолит потребуется ждать следующего цикла. Или делать внеочередной релиз с хот фиксом.
sobeskiller
Релизы + фиксы деплоят не потому что только так могут, а потому что это единственная правильная тактика чтобы крутить в проде стабильную систему. Автоматически деплоить в прод каждое изменение это конечно мощно!
rpc1
Довольно распространенная практика, называется Continuous Deployment
PerroSalchicha
Да и то, легко возразить по каждому пункту:
Масштабируемость у монолитов не особо и уступает микросервисам, вы точно так же можете добавлять инстансы по мере роста нагрузки. Да и сложность обновлений, это сильно преувеличенная проблема. Профиты от микросервисов в данном ключе совершенно незначительны по сравнению с привносимыми ими недостатками.
И в случае микросервисов, и в случае монолита, от распределённых команд требуется одно и то же - соблюдение контрактов у компонент приложения. И совсем не принципиально, как будет организована связность, через хттп в случае микросервисов, или через линковку в случае монолита.
Насчёт разных технологий - да, тут микросервисы придётся использовать, но следует упомянуть, что сама по себе ситуация, когда вы фрагментируете стек технологий в рамках одного проекта, это выстрел солью с перцем себе в жопу, и её надо избегать насколько это возможно.
Kanut
Но при этом в монолите вы не можете масштабировать конкретно ту часть системы, на которую выросла нагрузка. Вам нужно масштабировать всю систему.
Вам надо апдейтнуть какой-то небольшой функционал. У него есть зависимость от сторонней библиотеки и вам надо взять новую версию этой библиотеки. Но эта библиотека используется практически во всём монолите. Получается после небольшого апдейта вам надо тестировать практически весь функционал монолита.
Если у вас над одной монолитной кодовой базой работает 100500 человек, то вам будет очень весело согласовывать апдейты зависимостей или банально развлекаться с конфликтами в MR.
Как будто это всегда возможно. Ну то есть у нас половина проектов это интеграция решений от нескольких клиентов и их партнёров. Одни присылают сишные библиотеки, другие net core, третьи джаву, четвёртые питон, а интегрировать жто надо в какой-нибудь сименовский TIA, который до сих пор сидит на винформс или там в TestStand.
PerroSalchicha
Мне это не принципиально. У меня каждое обращение к монолиту происходит точно так же, как и в микросервисе - точка входа, внутренний сервис с бизнес-логикой, хранилище, снова тот же сервис, возврат. Если у меня на какую-то одну точку входа и один внутренний сервис увеличивается нагрузка, я тоже могу увеличить количество инстансов, и всё будет работать точно так же.
А с микросервисами у меня будут зависимости на пять разных версий данной библиотеки, а потом ещё и вылезет разное побочное поведение из-за фрагментации версий. Лучше уж протестировать, благо, в нормальной разработке это делается автоматически.
Почему? Наоборот, это будет делаться централизованно и контролируемо, ваши разработчики будут лишены возможности вот так с кондачка затянуть любую библиотеку, которая им понравилась, и будут вынуждены делать это осознанно.
Конечно, не всегда. Но случаев, когда в проект тянут команды с разными стеками просто потому, что всем пофигу, лишь бы работало, их куда больше, чем случаев, когда это делается реально вынужденно.
Kanut
И если вам это не принципиально, то это значит что всем остальным это тоже не принципиально?
И в чём проблема? У вас же раздельная кодовая база. Можете хоть разные стеки использовать.
Ну так тестирование никто не отменял. Но вам нужно каждый микросервис тестировать отдельно. И если он работает, то его можно не трогать и не тестировать пока не нужно что-то менять конкретно в нём.
То есть во первых у вас дополнительный организационный оверхед. Плюс если кому-то реально нужно аплейтить зависимости, то проблема остаётся.
Во первых хотелось бы посмотреть на эту статистику.
А во вторых даже если и так, то это тоже плюс для бизнеса. То есть он более свободен в вопросах найма новых сотрудников.
PerroSalchicha
Там суть была не в том, что оно лично мне не нужно, а в том, что масштабирование монолита не стоит дороже, чем масштабирование микросервиса.
Ну как, если мы говорим про абстрактную библиотеку, которая якобы везде используется, то она может влиять на что угодно, например, на десериализацию данных. Вот буквально на прошлой неделе у меня у самого была ситуация - две разных версии одной и той же библиотеки восстанавливали состояние воркфлоу по-разному, и в итоге начатый в одном приложении флоу в ряде случаев не мог быть продолжен во втором. Это не микросервисы, но проблема абсолютно аналогичной природы. Кодовая база должна быть синхронизирована.
Мы этим организационным оверхедом решаем куда более дорогой и неприятный оверхед дублирования кодовой базы.
Ну так а бизнесу нафиг не надо "более свободен". Найти столько специалистов, сколько нужно по одной и той же платформе - это всего лишь ещё одна задача для менеджера нижнего звена и эйчара. А сэкономить гроши на найме и фрагментировать платформу, это уже ловушка для всего бизнеса в целом, когда расходы на развитие и сопровождение софта кратно растут, и назад дороги нет. Если (когда) у вашего бизнеса наступит мёртвый сезон, при гомогенной кодовой базе он может просто сократить разработку и пережить этот период. При фрагментированной ему ещё надо будет платить за поддержание экспертизы в каждой из притянутой в проект технологии.
Kanut
У вас есть сервис с этой библиотекой. Он протестирован и работает без проблем. У вас есть другой сервис тоже с этой библиотекой. Тоже протестирован и работает.
Вам зачем-то надо апдейтнуть библиотеку для первого сервиса. Его теперь надо тестировать заново. Но зачем трогать второй?
Но с увеличением количества программистов эти оверхеды будут расти по разному. В какой-то момент ситуация поменяется.
М чего вы решили что можете говорить за весь бизнес?
До 2022-го это так просто не работало. Готовы были брать кого угодно. Ситуации разные бывают.
PerroSalchicha
Так я вам даже пример привёл. Второй сервис не надо тестировать, он-то свои юнит-тесты пройдёт. А вот что он пройдёт интеграционный тест, уже не факт, смотря за что библиотека отвечала, и каков был контракт между сервисами, надо будет тестировать.
Не поменяется, размер кодовой базы (а значит, и требуемое количество разработчиков) для монолита всегда будет меньше, чем кодовой базы для микросервисов. В монолите же у вас будут общие сервисы.
Опыт, прекрасное понимание проблем и задач бизнеса. Конечно, притянуть за уши какую-то надуманную ситуацию всегда можно, но ладно, давайте я буду говорить за 99.99% бизнеса, что это поменяет?
Это не из-за нехватки профильных программистов на рынке, а просто потому, что денег в индустрии было завались, на разумное их использование всем было начхать. Сейчас уже такой лафы нет, пора привыкать проектировать архитектуры, выбирать технологии, и подбирать себе штат.
Kanut
В его поведении ничего не изменилось. Почему он не должен пройти тесты если он проходил их до этого?
Контракты не менялись в этом примере.
Но разработчиков микросервисов нужно меньше координировать. Они могут работать полностью независимо если не меняются контракты.
У меня опыт другой.
Неважно почему. Ситуация такая была.
Сейчас нет. Завтра будет. Или послезавтра. Или...
PerroSalchicha
Зато изменилось в поведении другого сервиса, который вы обновили. А откуда вы можете быть уверены, что его поведение в интеграции с другими сервисами теперь соответствует контракту?
Контракт, это такая хитрая штука, которая как и тесты, не имеет 100% покрытия всего поведения. И ошибки любят возникать как раз в интеграции компонент, а не внутри. Те, которые внутри, которые явно нарушают контракт, отлавливаются легко.
Или наоборот, у разработчиков микросервисов должен быть свой выделенный внутренний микроменеджмент, чего не требуется в случае монолита.
Важно. Причина "я не делаю, потому что не имею возможности", это аргумент, а "я не делаю, потому что мне пофиг", уже не аргумент :)
Да уже прям сейчас. Рыночек-то поменялся.
Kanut
Вы это сейчас серьёзно? Если поведение другого сервиса ломает систему, то это проблема того самого другого сервиса. Этого не должно происходить если контракты не меняются.
Эээ, что?
Зачем?
И опять: эээ, что?
Рыночек в мире меняется каждые 5-10 лет.
PerroSalchicha
Абсолютно серьёзно. Этого не должно происходить, но тем не менее, это как раз та самая категория проблем, которая
а) происходит
б) проходит через юнит-тесты
Контракт, повторюсь, практически никогда не имеет 100% покрытия. И сразу пояснение, раз уж спросили: в контракте может не быть влияющих на работу таймаутов, может быть упущен какой-то вид исключений, может не учитывать сайд-эффекты десериализации и так далее. Поэтому если у вас код критикал, его нужно дополнительно тестировать, несмотря на то, что он проходит на соответствие контракту.
Потому что если у нас микросервисы пишут разные команды, у них там и разные менеджеры будут. Это как раз в порядке вещей. А если разные программисты в одной команде, то у них вот вообще никак не будет проблемы синхронизировать между собой кодовую базу.
Там всё настолько просто, что мне тут даже непонятно, что у вас вызвало непонимание :)
Kanut
Не могу припомнить ни одного случая в моей практике. Наверное мы что-то делаем не так...
Совсем не обязательно. Но речь и не об этом.
Если у вас все программисты, которые работают над всей вашей системой, вмещаются в одну команду, то естественно проблем не будет.
Но что вы будете делать если у вас над вашей системой работают сотни, а то и тысячи программистов?
Формулировка. Вообще непонятно что вы хотели этим сказать.
brutfooorcer
Чисто из интереса - каков размер самого большого монолита, с которым вы непосредственно работали? И каким был размер команды разработчиков?
rpc1
Я бы добавил еще один минус, с котором сталкивался в монолитах на java - это обновление версии библиотек или самой JVM, поэтому встречал корпоративный продукты которые работали на JDK 1.6, когда уже 11 версия существовала. Те же Spring обновления бывают ломают все и требуют обновление других компонентов и не понятно где это все вылезти может. А микросервисы намного проще в этом плане их можно постепенно переводить на актуальные версии библиотек и не доставлять боль другим.