Привет, Хаброжители! Сегодня мы делимся с Вами переводом статьи о распределенном монолите.

Аннотация

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

Мы подробно описываем коварные симптомы: кошмары версионирования, паралич развертывания и эрозия автономии команды. На ярком примере из реальной жизни — системе «Drive» и доставки на дом Carrefour — мы раскрываем основную проблему: внутреннюю модель, удерживаемую внешними стандартами. Затем мы раскрываем освобождающие решения: принятие по-настоящему нативных бизнес-моделей и разрыв цепей общих «основных» библиотек кода в пользу явного промежуточного программного обеспечения и надежных API-контрактов. Это путь не только к коду, но и к возвращению обещаний микросервисов.

1. Обещание и опасность

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

2. Обманчивое очарование общего «основного домена»

В основе этого обмана лежит, казалось бы, логичный выбор разработки: общая библиотека «core domain». Рассмотрим традиционное монолитное приложение. Чтобы обеспечить согласованность и соблюдать принцип «Не повторяйся» (DRY), архитекторы разумно отделяют основную бизнес-логику от технической инфраструктуры. Они могут создать пакет com.mycompany.core.domain, содержащий основные сущности, такие как Product, Customer или Order. Все части монолита зависят от этого. Это хорошо понятный паттерн для организации кода, централизации критически важных бизнес-правил и формирования общего понимания — единого языка — в рамках одного приложения.

// В монолитном приложении это может быть основная сущность домена.
package com.mycompany.core.domain;
public class Order {
private String orderId;
private OrderStatus status; // ОЖИДАНИЕ, ПОДГОТОВКА, ДОСТАВЛЕНО
private List<OrderItem> items;
private BigDecimal totalAmount;
// … сложные бизнес-правила, касающиеся проверки заказов, рекламных акций
}

Эта модель, столь эффективная в монолитной архитектуре, стала песней сирены для архитекторов микросервисов. Идея была проста: «Если наши основные бизнес-концепции настолько важны и общие для всех сервисов, почему бы не объединить их в общую библиотеку?» И именно здесь и затаилась коварная ловушка. То, что начиналось как паттерн для внутренней организации, быстро превращается в разрушительный антипаттерн в распределенной среде: «общий основной домен» становится узким местом и, в конечном итоге, распределенным монолитом.

Очень важно отличать этот подход от необходимого совместного использования контрактов API или определений сообщений. Проблема заключается не в совместном использовании структуры данных (например, DTO для сообщения о событии или определения OpenAPI), а в совместном использовании внутренней бизнес-логики, которая часто развивается и вынуждает службы синхронизировать обновления.

Почему? Потому что когда десятки микросервисов, каждый из которых в идеале является автономным, вынуждены зависеть от единственного, постоянно развивающегося файла core-domain-1.2.3.jar, их независимость исчезает.

  • Версионная проблема: незначительная настройка OrderStatus в общем core-domain.jar внезапно требует скоординированного обновления и повторного развертывания во всех зависимых службах. Это не независимое развертывание, а вынужденная синхронизация.

  • Паралич развертывания: ваш конвейер непрерывного развертывания, когда-то бывший предметом гордости, останавливается. Запросы на слияние становятся кошмаром, сопровождающимся каскадными изменениями во всех репозиториях. Обещание гибкости исчезает.

  • Утрата автономии: команды разработчиков, которые когда-то имели полную свободу в управлении своими сервисами, теперь оказываются скованными. Их прогресс диктуется самым медленным общим знаменателем — необходимостью идти в ногу с постоянно меняющимся «ядром». Единый язык становится натянутым, поскольку контексты вынуждены подстраиваться под единое монолитное определение. Преимущества микросервисов — независимое развертывание, автономная масштабируемость, гибкость команды — теряются. Остается распределенная система со всеми накладными расходами на координацию монолита, а также дополнительными сложностями, связанными с задержками в сети, распределенными транзакциями и согласованностью данных между разрозненными сервисами.

3. Испытание Carrefour: практический пример распределенного монолита

Рассмотрим такой гигант, как Carrefour, который справляется со сложностями своих услуг «Drive» (заказ и самовывоз) и доставки на дом. В его основе лежит система управления заказами (OMS), координирующая работу микросервисов: StockManagementService, DeliverySlotBookingService, FulfillmentService (для комплектации и упаковки) и BillingService. В начале своего пути в мире микросервисов Carrefour столкнулся с важным решением: как моделировать свои основные сущности, такие как «Заказ» или «Продукт». Сильно соблазнительно принять стандартизированную модель «Спецификация заказа» от крупного поставщика ERP. Она выглядит лаконичной, всеобъемлющей и «соответствующей отраслевому стандарту».

// Вариант A: Принятие общей модели ERP Order (заманчиво, но часто проблематично внутри компании)
package com.carrefour.erp.standard;
public class StandardOrder {
private String erpOrderId;
private String sapCustomerCode;
private String paymentTermCode;
// … многие области, имеющие отношение к общей системе ERP, не обязательно уникальные потребности Carrefour
}

Однако бизнес Carrefour уникален. Его программы лояльности, специфические механизмы продвижения, уникальные процессы управления запасами для «Drive» по сравнению с магазинами и взаимодействие с клиентами отличаются от других компаний. Принятие общей модели ERP в качестве внутренней основы означало бы втискивание тонких особенностей Carrefour в чужую структуру. Вместо этого появляется более эффективный путь: разработка моделей, присущих экосистеме Carrefour. Это означает, что их внутренний универсальный язык для «Заказа» (CarrefourOrder), «Продукта» (CarrefourProduct) или «Клиента» (CarrefourCustomer) будет точно отражать уникальные нюансы их бизнеса.

// Вариант B: Модель заказа, разработанная Carrefour, отражающая конкретные бизнес-правила.
package com.carrefour.oms.domain; // Часть ограниченного контекста управления заказами
public class CarrefourOrder {
private String carrefourOrderId;
private CarrefourCustomer customer;
private List<CarrefourOrderItem> items;
private LoyaltyPointsAccrual loyaltyAccrual; // Особенность Carrefour
private DriveSlot driveSlot; // Особенности Carrefour Drive
private DeliveryAddress deliveryAddress; // Специфично для доставки на дом
private CarrefourPromotion appliedPromotion; // Сложная внутренняя логика продвижения
// … методы, инкапсулирующие уникальные правила выполнения заказов Carrefour
}

Стандартный ERP-маппинг будет происходить только на границе системы, в антикоррупционном слое, когда данные должны быть обменены с внешней ERP-системой или сторонним платежным шлюзом. Это стратегическое решение освобождает внутренние службы Carrefour от жестких ограничений внешних моделей. Но даже с нативными доменными моделями может возникнуть антипаттерн «общей базовой библиотеки». Если CarrefourOrder и его сложная логика валидации находились бы в общем carrefour-core-domain.jar, унаследованном каждым микросервисом (OMS, Fulfillment, Billing и т. д.), ситуация быстро стала бы критической:

<dependency>
<groupId>com.carrefour</groupId>
<artifactId>carrefour-core-domain</artifactId>
<version>1.2.3-SNAPSHOT</version>
</dependency>

Представьте себе новую промоакцию Carrefour, которая изменяет LoyaltyPointsAccrual в CarrefourOrder. Это небольшое изменение в carrefour-core-domain.jar вызовет цепную реакцию:

  • Выпущена новая версия JAR 1.2.4-SNAPSHOT.

  • Выпущена новая версия JAR 1.2.4-SNAPSHOT.

  • Команда OMS должна обновиться до версии 1.2.4.

  • Команда FulfillmentService должна обновиться до версии 1.2.4.

  • Команда BillingService должна обновиться до версии 1.2.4.

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

4. Избегая монолита: возвращение автономии

Путь к освобождению предполагает дисциплинированное возвращение к основным принципам DDD и прагматичный подход к совместному использованию кода.

4.1. Истинные границы домена: ограниченные контексты и единый язык

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

  • Декомпозиция, а не централизация: вместо одного монолитного «основного домена» определите независимые контексты.

  • Нативные модели правят балом: настаивайте на использовании внутренних моделей, которые точно отражают особенности вашего бизнеса, а не являются готовыми стандартными решениями. CarrefourOrder адаптирован к промоакциям и логистике Carrefour. Только на внешней границе, возможно, в рамках антикоррупционного уровня, CarrefourOrder сопоставляется с общим StandardERPOrder для интеграции с устаревшей системой. Это защищает вашу внутреннюю гибкость.

  • Коммуникация, управляемая событиями: изменения в одном ограниченном контексте транслируются в виде событий. Например, контекст «Order Promise» может генерировать событие OrderPromiseValidated. Контекст «Выполнение», заинтересованный в выполнении подтвержденных заказов, подписывается на это событие и поддерживает собственную внутреннюю модель FulfillmentOrder на основе данных события. Такая слабая связь через события значительно снижает прямые зависимости. Предметно-ориентированное проектирование предлагает шаблоны, такие как Customer-Supplier, которые описывают, как контексты могут взаимодействовать в качестве потребителей и поставщиков API, и предупреждает об опасностях плохо управляемого Shared Kernel, который часто является причиной распределенного монолита.

4.2. Окончательное избавление от общих библиотек кода

Общая библиотека кода «основного домена» является основной проблемой распределенного монолита. Ее необходимо устранить.

API-First для бизнес-логики: если сервису необходимо взаимодействовать с основной бизнес-логикой другого сервиса (например, сложные правила ценообразования Carrefour), он должен делать это через четко определенный API с версиями, а не путем наследования общей библиотеки. BillingService вызывает API PricingService; он не импортирует PricingServiceLogic.jar. Java // Вместо:

// import com.carrefour.pricing.core.PricingCalculator;
// … PricingCalculator.calculateFinalPrice(order);

// Используйте вызов API:

// PricingService.calculatePrice(orderId).subscribe(price -> …); // Via REST or gRPC
  • Библиотеки промежуточного программного обеспечения для технических утилит: если и существует действительно повторяющийся код, то он должен быть техническим или инфраструктурным, а не основной бизнес-логикой. Подумайте о утилитах ведения журналов, фреймворках безопасности или парсерах общих форматов данных. Они относятся к специализированным библиотекам промежуточного программного обеспечения с версиями.

XML <dependency>
<groupId>com.carrefour.infra</groupId>
<artifactId>carrefour-logging-utils</artifactId>
<version>1.0.0</version>
</dependency>
  • Важно отметить, что отдельные микросервисы используют их через стандартное управление пакетами (например, Maven, npm) и обновляют их в своем собственном темпе, сводя к минимуму принудительную синхронизацию. Такой подход, основанный на явных контрактах и коммуникации на основе сообщений, также имеет основополагающее значение для автономности данных, позволяя каждому сервису владеть и управлять своей собственной базой данных, тем самым избегая связей на уровне схемы базы данных — еще одной частой проблемы распределенного монолита. Для определения этих контрактов сообщений и генерации заглушек можно использовать такие инструменты, как Protobuf или Avro, которые предлагают надежный подход без совместного использования бизнес-логики.

4.3. Расширение возможностей команд и архитектур

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

  • Команды, ориентированные на домен: организация команд вокруг ограниченных контекстов способствует углублению экспертных знаний в области домена и снижает необходимость межкомандной координации при внутренних изменениях модели домена. Это соответствует закону Конвея, который гласит, что структура системы будет отражать структуру коммуникации в организации. Чтобы избежать распределенного монолита, структуры команд должны отражать автономность сервисов и ограниченных контекстов.

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

  • Надежные API-контракты: формализуйте API-контракты с помощью таких инструментов, как OpenAPI/Swagger. Это устанавливает четкие границы и позволяет осуществлять независимую эволюцию, обеспечивая обратную и прямую совместимость. Формальные API-контракты также имеют решающее значение для упрощения распределенного тестирования и отладки, поскольку они четко определяют точки интеграции.

5. Заключение

Возвращение к мечте о микросервисах

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

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

Выбор за вами.

Комментарии (1)


  1. svetayet
    31.10.2025 10:12

    Авторы, вы сами перечитывали свою статью ?

    "Это путь не только к коду, но и к возвращению обещаний микросервисов. " - какой такой "путь к коду"?

    И далее в статье язык просто капец. Таким языком героическое фэнтези для затуманивания мозгов писать, а не нормальные лаконичные технические статьи с доводами и аргументами.