Всем привет, меня зовут Сергей Прощаев.
Руковожу направлением Java-разработки в FinTech и преподаю на курсах архитектуры в ОТУС, и за годы работы я видел много архитектурных катастроф. Знаете, что их объединяет? Чаще всего это не падение базы данных и не «кривые руки» разработчиков. Виновником оказывается непродуманная интеграция. Команда соединяет два приложения, надеясь, что они подружатся, а в итоге получают монстра типа Франкенштейна, который падал каждую ночь.
В этой статье я хочу погрузить вас в тему «Стилей интеграции». Мы разберемся, почему передача файлов — это зло для реального времени, как общая база данных превращается в антипаттерн, и почему асинхронный обмен сообщениями (Messaging) часто становится спасательным кругом, хотя и требует перестройки мышления.
Покажу, как эти подходы работают (или не работают) в реальной жизни, приведу примеры из практики и расскажу, какие Best Practice используют команды, чтобы их прод не разлетался на куски при первой же DDoS-атаке или сбое смежного сервиса.
Поехали!
Интеграция — это не про код, а про договоренности
Когда мы пишем код внутри одного монолита, всё просто: вызвал метод, получил ответ. Но в мире распределенных систем всё иначе. У нас есть несколько критериев, которые превращают простую задачу «передать данные» в архитектурный квест.
Оглядываясь на свой опыт — подтверждаю актуальности тех главных «китов», изображенных на рисунке 1:
Связывание (Coupling). Самое страшное для архитектора — это жесткая привязка. Если изменение в приложении А ломает приложение Б — вы проиграли. Интерфейсы должны скрывать внутреннюю реализацию.
Надежность (Reliability). Локальный вызов падает крайне редко. Удаленный вызов — это игра в русскую рулетку. Сеть может лечь, сервис может перезагружаться, а таймауты — не лечатся магией.
Своевременность (Timeliness). В идеальном мире получатель узнает о данных в ту же секунду, как они появились. Но гонка за скоростью часто приводит к тому, что мы забиваем на консистентность.

В сети есть описание одного проекта в ритейле. Команда интегрировала складскую систему с интернет-магазином. Данные передавались файлами раз в час. Всё было «по книжке», пока в черную пятницу у нас не упал склад. Оказалось, что файл «остатки.xml» весил уже под 2 ГБ, парсился 40 минут, и за это время товары на сайте уходили в минус. Команда жестко связала два приложения форматом файла и временем доставки. Как оказалось на практике — это было ошибкой.
Давайте пройдемся по основным стилям интеграции, чтобы понять, как избежать подобных граблей.
Стиль 1. Передача файлов (File Transfer)
Самый «древний» и, на первый взгляд, простой способ. Приложение А пишет файл в определенную директорию. Приложение Б его забирает, парсит и делает что-то полезное.
Плюсы:
Слабая связанность. Отправителю все равно, кто и как читает файл.
Простота отладки. Файл есть — данные есть.
Минусы:
Задержки. Как в примере со складом, частота создания файлов всегда будет компромиссом.
Семантический диссонанс. У одного приложения в поле «Статус» может быть значение «1», а у второго — «ACTIVE». Приходится писать горы трансляторов.
Операционные сложности. Куда девать файлы после обработки? Как обрабатывать сбои? Создание и обработка тысяч мелких файлов убивают I/O систему.
Я часто вижу это в legacy-системах. Если ваша задача — раз в сутки загрузить справочник контрагентов, файл — отличный выбор. Но если вы пытаетесь строить на этом реальное взаимодействие, вы обречены на вечную «рассинхронизацию».
Стиль 2. Общая база данных (Shared Database)
Классика жанра: приложения не обмениваются данными напрямую, а пишут и читают из одной и той же базы данных.
Плюсы:
Консистентность. Транзакции ACID решают много проблем.
Скорость разработки. На старте проекта кажется, что это самый быстрый способ.
Минусы:
Жесткая связь. Изменение схемы данных одним приложением рушит все остальные. Я видел, как DevOps-инженеры плакали кровью, пытаясь сделать rolling update приложений, работающих с одной БД.
Проблемы масштабирования. База данных становится единой точкой отказа и узким местом.
Нарушение инкапсуляции. Приложения получают полный доступ к чужим данным, нарушая бизнес-логику. Приложение может случайно (или намеренно) изменить данные, за которые отвечает другой сервис.
Сейчас, в эпоху микросервисов, это считается антипаттерном. Однако в DDD (Domain-Driven Design) мы иногда используем «Shared Kernel», но это скорее исключение для очень тесно связанных команд, а не правило для всей компании.
Стиль 3. Удаленный вызов процедур (Remote Procedure Invocation)
Здесь мы начинаем говорить про синхронное взаимодействие: REST, SOAP, gRPC, CORBA (кто помнит?), .NET Remoting. Для нас, разработчиков, это самый комфортный паттерн, потому что он имитирует локальный вызов функции.
Плюсы:
Простота модели. Вызвал метод — получил результат.
Инкапсуляция. Приложение предоставляет четкий API, скрывая внутренности.
Минусы:
Время жизни. Удаленный вызов медленнее локального в сотни, если не в тысячи раз.
Надежность. Синхронный вызов требует, чтобы и клиент, и сервер были доступны одновременно. Если сервис авторизации лег, вся система ложится.
Сильная связанность. RPC характеризуется «достаточно сильной степенью связывания». Изменение контракта — боль для всех потребителей.
В одном из проектов мы пытались построить систему обработки платежей полностью на синхронных REST вызовах. Пока всё работало — было отлично. Но в момент, когда у нас легла сеть между дата-центрами, мы поняли, что мы не просто потеряли 10% функционала — мы уронили всё, потому что цепочка вызовов была выстроена последовательно. Именно тогда я начал глубже копать в сторону асинхронности.
Стиль 4. Асинхронный обмен сообщениями (Messaging)
А теперь — мой фаворит. Тот самый подход, который позволяет спать спокойно, когда падают соседние сервисы.
Вместо прямого вызова мы используем брокер сообщений (RabbitMQ, Kafka, ActiveMQ). Отправитель (Producer) просто кидает сообщение в канал (Message Channel) и забывает про него. Получатель (Consumer) забирает его, когда будет готов.
Почему это эффективно?
Слабое связывание. Отправитель не знает, кто получит сообщение. Можно добавить 10 новых потребителей, даже не уведомляя источник.
Надежность. Не требуется одновременной доступности. Если получатель упал, сообщение сохранится в очереди и будет доставлено при восстановлении.
Устранение семантического диссонанса. В цепочку можно добавить Message Translator, который на лету сконвертирует формат данных, не меняя код исходных приложений.
Однако, как и у любой технологии, у Messaging есть обратная сторона медали. Во-первых, это сложность мышления. Разработчики привыкли к синхронности. Когда я начинал внедрять асинхрон, команда говорила: «Как мы будем отдавать ответ пользователю?». Приходилось переучиваться на event-driven архитектуру.
Во-вторых, это сложность отладки. Проследить цепочку: «Пользователь нажал кнопку -> Сообщение попало в очередь -> Обработалось -> Отправилось в другой сервис» гораздо сложнее, чем дебажить стек вызовов. Но современные трейсинги (OpenTelemetry, Jaeger) решают эту проблему.
Чтобы было понятнее, как выглядит классический асинхронный обмен, вот пример простой диаграммы, изображенной на рисунке 2.

История из жизни про то как TSB Bank чуть не канул в лету
Говоря о цене ошибок в интеграции, нельзя не вспомнить реальный кейс 2018 года — крах TSB Bank. Ребята решили переехать на новую платформу Proteo4UK. Миграция данных затрагивала всех клиентов банка — около 5,4 млн человек. Но вместо постепенного подхода они выбрали «big bang» — переход в один момент.
В чём была проблема с точки зрения стилей интеграции? Они полагались на синхронные вызовы и прямые интеграции без должной буферизации. Когда система после миграции вышла в прод, она не выдержала нагрузки. Около 1,9 млн клиентов потеряли доступ к онлайн- и мобильному банкингу. Оказалось, что сценарии обработки сбоев и поведение при нестандартных нагрузках (включая внезапный вал обращений) не были проработаны.
Это классический случай игнорирования принципа асинхронности и недооценки связанности. Банк пытался сделать всё синхронно и «в один момент». В итоге клиенты потеряли доступ к счетам на недели, а убытки банка исчислялись сотнями миллионов фунтов.
Если бы они использовали подход с очередями сообщений (Messaging), они бы смогли «растянуть» миграцию во времени, поставить задачи в очередь и обрабатывать их по мере готовности системы, не роняя фронтальные сервисы. Это идеальный пример того, как правильный стиль интеграции спасает бизнес, а неправильный — его убивает.
Best Practice: как мы строим интеграции сейчас
Опираясь на случай с TSB и свой личный опыт, я выработал для себя несколько железных правил (Best Practice), которые мы внедряем в командах:
Разделяй синхронное и асинхронное.
Если пользователь ждет ответ (например, «оплатить заказ»), используй синхронный вызов, но с четкими таймаутами и паттернами вроде Circuit Breaker (предохранитель). Если это фоновая задача («отправить уведомление» или «пересчитать витрину данных») — только асинхрон.События — это новый стандарт.
Переходи на Event-Driven Architecture (EDA). Сервисы не должны вызывать друг друга, чтобы узнать, что произошло. Сервис должен публиковать событие (UserRegisteredEvent, OrderPaidEvent). Все заинтересованные подписываются. Это минимизирует связанность до предела.Контракты важнее реализации.
Используй такие инструменты, как AsyncAPI (для асинхрона) или OpenAPI (для синхрона). Спецификация должна быть «источником истины». Мы храним контракты в репозитории и используем генерацию клиентов. Это исключает ситуацию, когда разработчики вручную пишут JSON-парсеры и ошибаются в названиях полей.Message Broker — не база данных.
Очень частая ошибка: пытаться использовать Kafka как основное хранилище. Очередь — это транспорт. Если вам нужно гарантировать доставку и обработку — пожалуйста. Но если вы храните состояние в очереди и не выгружаете его в базу, вы рискуете потерять данные при перезапуске брокера.Проектируй отказоустойчивость (Idempotency).
В асинхронном мире сообщение может прийти дважды. Это факт, с которым надо смириться. Потребители сообщений должны быть идемпотентными. Если вы дважды обработаете один и тот же платёж — это катастрофа. Поэтому всегда проверяйте уникальный идентификатор (IDempotency Key).
Заключение: стиль — это диагноз!
Подводя итог, скажу так: выбор стиля интеграции — это не технический выбор технологии (RabbitMQ vs REST). Это выбор философии взаимодействия между вашими сервисами.
Если вы выбрали файлы — готовьтесь к задержкам и администрированию.
Если выбрали общую базу — готовьтесь к проблемам при масштабировании.
Если выбрали синхронный RPC — готовьтесь к сложностям с отказоустойчивостью и ростом связанности.
Если выбрали асинхронный обмен сообщениями — готовьтесь перестраивать мышление команды, но получите надежность и гибкость.
Хороший архитектор, который смотрит в будущее, должен уметь комбинировать эти подходы. В одном контуре у вас может быть быстрый gRPC для внутренних микросервисов с низкой задержкой, а для интеграции с внешними партнерами — Kafka с долгим хранением сообщений.
Чтобы научиться чувствовать эти грани, проектировать надежные системы и не повторять ошибок TSB, нужно не просто читать книги, а практиковаться, обсуждать кейсы и понимать, как работают эти механизмы под капотом.
Если вы хотите глубже разобраться в архитектуре программного обеспечения, научиться выбирать правильные стили интеграции и уверенно проходить технические собеседования, то самое время присмотреться к курсу «Microservice Architecture» в OTUS.
На курсе мы разберем реальные кейсы, построим диаграммы последовательности и поймем, как не наступить на те грабли, о которых я рассказал выше. Чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест (до 30 апреля за прохождение теста действует скидка 15% на курс).
Больше курсов по разработке и ИТ-архитектуре смотрите в каталоге.
