Предисловие

Исследуя технологические стартапы на разных стадиях развития, я неоднократно наблюдал возникновение монолитных систем в начале жизненного цикла платформы. У разработчиков этих первоначальных монолитных систем, как правило, есть веские причины для поиска разумных компромиссов для оптимизации скорости продвижения. В то же время у достаточно устойчивых компаний, которым удалось достичь соответствия продукта требованиям рынка, может в одночасье возникнуть потребность в масштабировании. В такой “переломный” момент сложные проценты по технологическому долгу могут вызывать инерцию, которая со временем значительно замедлит прогресс платформы. Конфликт, связанный с переходом от экономии на скорости продвижения к экономии на масштабировании, — вот с чем сталкивается “монолитный герой”, превращающийся в “многоликого (распределенного) злодея”.

В этой статье я поделюсь своим более чем 20-летним опытом работы с различными монолитными системами, создающими коммерческое программное обеспечение на основе веб-технологий. Выявление признаков того самого “переломного” момента имеет решающее значение для обеспечения успешного масштабирования организации и минимизации проблем роста.

Первоначальный монолит

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

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

Технические монолиты

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

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

Ключевые инвестиции в проектирование

  • Инженерный тип мышления — изучите технические требования, начиная от фронтенда и заканчивая бэкендом, но выполняйте их в обратном порядке — от бэкенда к фронтенду.
  • Предметно-ориентированный подход — начинайте со специально созданных сущностей между доменами и агрегатами (ссылка).
  • Модулированный код — разделите функциональность для подготовки к рефакторингу кода или удаленному размещению общих пакетов (ссылка).
  • Схемы баз данных — отдельные схемы для каждого ограниченного контекста.
  • Развертывание служб без разделения — регулярно убеждайтесь в возможности развернуть каждый компонент независимо.

Монолит организации — растущая команда

На этой стадии, если продвижение минимально жизнеспособного продукта (MVP) набирает обороты, компания, скорее всего, получит финансирование, и наступит период роста. Это неизбежно означает, что к команде присоединятся новые люди, которым инженер передаст свое видение.

На ранних этапах, когда команда небольшая (2-15 человек), рабочие процессы, как правило, не связаны между собой, так как накладные расходы на согласованное выполнение требований являются управляемыми. Однако приходит время, когда команда разрастается до такой степени, что не может эффективно справляться со всем объемом работы. Тогда принимается решение разделить команду, сформировав небольшие группы, способные быстрее и успешнее реагировать на задачи компании.

На ранних этапах жизненного цикла любой организации обычно создаются монолитные структуры, ориентированные на простые решения (принцип KISS — Keep It Stupidly Simple — ничего не усложняйте). Впоследствии приходится придерживаться еще более взвешенного подхода (принцип YAGNI — You Ain’t Gonna Need It — не делайте то, что никогда не понадобится). Такой подход, экономящий усилия команды, обеспечивает минимально жизнеспособный продукт (MVP) в ущерб качеству.

Здесь возникает риск иллюзии неведения, когда ваш взор упирается в линию горизонта и вы не в состоянии заглянуть за нее. Требуется особая прозорливость, чтобы предвидеть грядущие вызовы и подготовиться проактивно отвечать на них. Иначе придется столкнуться с цепью бесконечных реактивных действий.

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

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

Ключевые технические инвестиции

  • Удаленная публикация кода — начните публиковать общий код в удаленный репозиторий несмотря на кратковременное увеличение накладных расходов.
  • Изолированные развертывания — проведите рефакторинг независимо развертываемых бэкенд-сервисов в их собственные репозитории.
  • Оптимизация коммуникационной структуры организации — воспользуйтесь шансом извлечь выгоду из низкозатратного проактивного формирования команды для достижения успеха уже сейчас. В противном случае вы будете страдать от того, что позволите коммуникационной структуре организации диктовать тип программного обеспечения, поставляемого вами (ссылка).
  • Документация — потратьте время на создание вики-сайта с продуманной информационной архитектурой, чтобы направить коллективную мудрость организации на создание полезного контента. Коллективные знания укрепляют сплоченность команды.

Семь технологических монолитов

В этом разделе рассмотрим семь наиболее распространенных технологических монолитных паттернов, с которыми мне приходилось сталкиваться в стартапах.

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

Классические монолиты:

  • (1) Монорепозиторий.
  • (2) Монолитное приложение.
  • (3) Монолитный сервис.
  • (4) Монолитная база данных.

Продвинутые монолиты:

  • (5) Распределенный монолит.
  • (6) Облачный монолит.
  • (7) Монолит на GraphQL.

Далее разберем анатомию каждого типа монолитов с описанием возможных рисков и советами по их предотвращению.

(1) Монорепозиторий

Пересечение показателей оптимальной продуктивности (PRODUCTIVITY) и количества членов команд (NUM OF TEAMS)

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

Размещение кода в одном репозитории имеет много преимуществ на ранних стадиях. Но наступает момент, когда эти преимущества работают против вас, потому что слишком много проблем смешиваются воедино:

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

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

Увеличение затрат на задержку перепроектирования монорепозитория в мультирепозиторий (дисклеймер: только в целях иллюстрации)

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

(2) Монолитное приложение

Снижение продуктивности

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

Область применения небольшого приложения

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

Расширение области применения

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

Разграничение области применения

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

(3) Монолитный сервис

Система с беспорядочной структурой без разделения функциональности

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

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

  • Сущности, поля которых являются прилагательными.
  • Сущности, у которых есть поля, являющиеся глаголами.
  • Сущности с подозрительно большим количеством полей.
  • Отсутствие слоев в коде.
  • Несогласованные шаблоны доступа.

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

Стратегия рефакторинга монолитного сервиса

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

  • Завершите работу с помощью DRY (Don’t Repeat Yourself — не повторяйся), удалив первоначальный код.

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

(4) Монолитная база данных

Междоменные соединения

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

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

(5) Распределенный монолит

Тесно связанные распределенные зависимости

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

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

Многослойное разделение функциональности
  • Правило 1: сервисы оптимизированы для совокупной бизнес-логики.
  • Правило 2: сервисы оптимизированы для бизнес-правил, основанных на домене, и персистентности данных.
  • Правило 3: трафик всегда направлен на юг и не пропускает уровни.

Руководствуясь этими правилами, команда сможет самостоятельно масштабироваться, подбирая сценарии использования в соответствии с конкретными требованиями компании.

(6) Облачный монолит

Такое облако может быть как чрезвычайно эффективным, так и экстремально опасным 

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

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

Тесная связь на уровне инфраструктуры

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

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

(7) Монолит на GraphQL

GraphQL Federation

В то время как традиционная архитектура API может разрастаться по мере увеличения количества доступных конечных точек, этот тип монолита характерен для GraphQL благодаря возможностям, которые дает такая технология для обхода всего API. Последний монолит, на котором я хочу остановиться, — GraphQL Federation, используемый как единый монолитный API для всего.

Проблемы с производительностью и безопасностью

Проблема возникает, когда федерация разрастается до такой степени, что становится неуправляемой. Со временем при добавлении новых API придется контролировать связанные с этим изменения, проверяя, не введено ли то, что нежелательно объединять с другой частью графа.

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

Федерацию можно эффективно применять

Рекомендация такова: если есть желание использовать федерацию, старайтесь не сваливать все в монолитный суперграф. Вместо этого проведите границы намерений вокруг цели каждого графа федерации. Они могут быть проведены по приложениям, разделить функциональные возможности приложений, локализовать область безопасности и т. д.

Заключение

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

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи chubernetes: Evolution of Monolithic Systems

Предыдущая статьяИспользование Snowflake для прогнозирования эскалации в колл-центре
Следующая статьяВыполняйте загрузку Excel-файлов в Python в 1000 раз быстрее