
Как вы думаете, нужно ли архитектуру на вашем текущем проекте подвергнуть масштабному пересмотру и исправлению? Ставлю на то, что большинство читателей ответят положительно. И эта часть именно про это. В ней мы рассмотрим:
Когда сложившаяся архитектура подлежит масштабным изменениям.
Что не менее важно, когда лучше оставить, как есть.
Ключевые признаки проблем в архитектуре.
Основные способы исправления таких проблем.
Но для начала мы вспомним, что было в предыдущих сериях. В первой части мы прошлись по теории и выяснили:
Что техническая реализация заметно влияет на успехи бизнеса, хоть и не очень критично;
Что из всех аспектов технической реализации наибольший вклад в успех вносит именно архитектура;
Что самое важное свойство архитектуры — максимальная независимость команд друг от друга;
Что это свойство вытекает напрямую из двух фундаментальных характеристик программного обеспечения: coupling и cohesion, где coupling — характеристика связи двух точек системы/кодовой базы; а cohesion — характеристика того, насколько плотно упакованы такие связи в компоненты.
Во второй части мы уже перешли к практике построения архитектуры с нуля. Мы узнали:
Что попытки угадать с архитектурой до старта проекта обычно проваливаются.
Что маленькие команды работают буквально в разы эффективнее, чем большие.
Что лучший способ разделить софт между командами — делать это постепенно. Начать с одной команды и уже затем дробить систему по обнаруженным в процессе разработки границам.
Теперь перейдем к вопросу, что же делать, если «все уже украдено до нас».
Как понять, что нужно что-то делать?
Переработка архитектуры — это очень дорого. Поэтому решение на проведение крупных исправлений в области архитектуры и технических процессов должно быть экономически обоснованным. Я знаю огромную кучу успешных бизнесов, которые несмотря на сомнительную архитектуру продолжают зарабатывать очень большие деньги.
Но есть и другие истории, где даже лидер рынка, HP LaserJet, обнаружили себя в ситуации, где из 100% времени на разработку только 5% уходили на новые фичи, оставшиеся 95% — на что угодно кроме инноваций. После реорганизации с привлечением опытных консультантов‑технарей цифра выросла до 40%, то есть в 8 раз.

Позже, имея в виду этот кейс, исследователи из State of Devops обнаружили, что средние цифры все же несколько отличаются: сильные организации умудряются обычно тратить даже больше времени на новые фичи — до 50% в среднем. А если это средние цифры, то надо понимать, что есть даже те, у кого дела ещё лучше. То есть, у HP LaserJet был еще запас, куда расти, и могло быть даже не 8 раз, а больше 10.

В то же время надо понимать, что 5% на инновации — это ну уже совсем беда. В среднем слабые команды выдают 35%, что на фоне 50% у сильных — не так уж и плохо. Всего‑то в полтора раза, а не 8, как у HP.

Отсюда и ответ: если вы, как HP, находитесь на уровне, где ваша инновационная деятельность полностью парализована, то реорганизация может принести вам и десятикратный рост производительности. И это очень серьезный повод задуматься о новой архитектуре и процессах.
Однако, если вы не чувствуете, что эффект от роста числа новых фич окупит все расходы на новую архитектуру и процессы — реорганизация не для вас. Тем более это так, если вы уже находитесь на том среднем уровне в 35%, что намерили исследователи из State of Devops.
Хотя, если у вас 35%, но вы чувствуете, что эти ожидаемые 44% прироста в скорости разработки, дадут вам серьезное конкурентное преимущество, то это повод найти знающего специалиста и взяться за ваши архитектуру и процессы автотестирования и поставки.
Обратный маневр Конвея.
Закончив с мотивацией, приступаем к практике.
Еще в далеком 1967 году Мелвином Конвеем был подмечен тот факт, что дизайн системы неизбежно копирует структуру организации. Иными словами: архитектура системы больше всего зависит от топологии команд. Это наблюдение было названо Законом Конвея. Отсюда и следует очевидный вывод: если хотим изменить архитектуру, надо менять структуру организации. Такое мероприятие называется обратным маневром Конвея.
В самом простом варианте такая «смена структуры» будет представлять из себя просто переданный функционал из одной команды в другую. Перераспределение ответственностей между командами даёт большой ресурс по исправлению архитектурных проблем.
Например, одна команда отвечает за одну часть обработки разных типов заявок, другая — за другую. Исправления в одной команде всегда могут сломать заявку так, что в другой она не обработается и, следовательно, эти команды всегда вынуждены будут тестироваться совместно. Если мы хотим убрать этот coupling (см. 1 часть), имеет смысл рассмотреть перераспределение ответственностей. Например, дать обеим командам ответственность за обе части обработки, но одним отдать одни типы заявок, вторым — другие. После чего обе команды вырежут себе из старых кодовых баз то, что им нужно, и каждая сядет на свою кодовую базу. Это перераспределение приведет к тому, что команды никак не смогут друг другу что‑то сломать и более не обязаны тестироваться и релизиться совместно. Это классический пример обратного маневра Конвея.
Такое перераспределение возможно, если команды обладают всеми необходимыми для этого компетенциями. Но бывает и совсем иначе. Например, одна команда состоит из фронтендеров, другая из бэкендеров, третья из базистов, четвертая из девопсов, пятая из тестеров. Это антипример, но такое встречается. И тут реорганизация, скорее всего, будет подразумевать, что для сборки одной независимой команды, надо как минимум отщипнуть несколько человек от других. Получающиеся в результате подобных реорганизаций универсальные команды называют еще «кросс‑функциональными».
В подобном случае такое мероприятие, как правило, скорее полезно. Однако часто переброски людей между командами — весьма вредная затея: сработанная команда — это очень ценный ресурс, который надо беречь.

Иногда перераспределения ответственностей или компетенций недостаточно. Например, бывают организации, где все подразделения делают, по сути, один и тот же продукт, но каждое крутится вокруг своего конкретного клиента/рынка. В целом, идея выделить команду под группу клиентов — нередко очень правильная, например, в очень многих бизнесах физлица, юрлица и сотрудники имеют слишком разный функционал, чтобы игнорировать это при разделении.
Но, если речь идет о команде под конкретного, например, клиента или рынок, то тут уже могут быть нюансы. Если вы всем клиентам продаете один и тот же продукт, но у каждого из клиентов есть целая команда, которая работает на него, то это может привести к проблемам. Со временем оказывается, что команды начинают друг другу мешаться на одной кодовой базе, из‑за чего они всячески пытаются отгородиться друг от друга. Распилить один и тот же продукт на несколько одинаковых но чуть‑чуть разных не позволяет здравый смысл, поэтому часто разработчики придумывают хитрые механизмы версионирования. Кодовые базы, как правило, начинают существовать на нескольких ветках, которые периодически приходится в страшных муках интегрировать между собой.
На этом примере очень хорошо видно, как работает закон Конвея: некоторые структуры организации могут превращать архитектуру системы в кромешный ад. Поэтому очень важный вывод, который следует из закона Конвея: решения об организационной структуре технологических компаний — это далеко не только управленческое решение, но еще и техническое.
Как чинят организации из примера выше?
В таких ситуациях обратный маневр Конвея необходим. Вместо ориентации на рынок или клиента, подразделения организации должны ориентироваться на отдельные продукты и подпродукты. Команды разрабатывают свои продукты с учётом интересов всех потребителей сервиса. И если какому‑то клиенту не нужны фичи другого клиента, то они просто выключаются с помощью механизмов конфигурации.
Хороший пример такой трансформации тут.
Признаки связанности и их исправление.
Разобравшись с основным инструментом, теперь мы должны понять, где его применять надо, а где — не стоит.
Как понять, какую именно перестановку делать? Как и в случае рефакторинга, где есть код‑смеллы, тут тоже можно ориентироваться на определенные признаки, которые могут подсказать, что что‑то не так. И, как и в случае код‑смеллов, эти признаки являются не однозначными индикаторами проблемы — это всего лишь намёки, а дальше нужно включать голову самостоятельно и думать.
К слову, давайте с этих код‑смеллов и начнем. Оригинальные код‑смеллы из книжки Фаулера и Бека, по большей части, указывают именно на coupling‑связи, которые нам, как раз и нужны. Однако у них есть ограничение: они предназначены для кодовых баз. И вот тут очень удачно, что какая‑то их часть подходит даже для архитектурных масштабов. Специально для этой статьи я отобрал несколько таких код‑смеллов и взял на себя смелость адаптировать их под реалии архитектуры больших систем.
Код-смеллы, которые норм
Во‑первых, разберемся с важнейшими оригинальными код‑смеллами, которые НЕ являются признаками лишних coupling‑связей в больших архитектурах.
Большой сервис - код-смелл, который норм
Этот признак является идеологическим наследником код‑смеллов «Большой класс» и «Большой метод». В отличие от своих родителей, большой сервис не является проблемой сам по себе. Если команда все еще способна поддерживать и развивать весь функционал сервиса самостоятельно, то большой сервис часто — это даже более удачное решение (см. исключения ниже).
В таком случае, команде не придется нести накладные расходы, связанные с распределением функционала и кода по разным репозиториям и единицами поставки. Характерные проблемы разбиения больших сервисов в пределах одной команды:
разбиение кода на маленькие сервисы — очень часто приводят к проблемам распределенных систем: нарушениям консистентности, гонкам и т. д.;
менеджмент дублирующегося кода требует головняка с выпуском библиотек и их версионированием, тогда как в одной кодовой базе это дается бесплатно;
на 2 модуля нужно в 2 раза больше работы по развертыванию и поддержке;
чтение кода и правки становятся «рваными»: часть работы нужно сделать в одном репозитории, а часть — в другом.
если между модулями есть какая‑то связь, то тогда независимое тестирование модулей будет более рваным, чем если бы это был один модуль.
совместное тестирование модулей делать заметно сложнее, чем по отдельности.
необходимо поддерживать интеграционный код, обеспечивать его надежность и, при необходимости, способность держать нагрузку. Если бы функционал не был разбит, то эта часть была бы бесплатной.
Это все говорит о том, что coupling никуда не теряется, но cohesion уменьшается. В целом, проблемы терпимые и решаемые, но тут вопрос в том, что вы получаете за них? Как правило, ничего.
Возможное исключение из правила: когда команде приходится поддерживать несколько сервисов под разные предметные области. Если у команды куча очень разных и далеких друг от друга задач, то пихать их все в один сервис может оказаться плохой идеей. В такой ситуации вы рискуете правкой в одном участке поломать другой, совсем с ним не связанный. И, поверьте, стейкхолдеры, такого не поймут. Тогда единственным минусом от распределенности будет только развертывание нескольких модулей вместо одного. При этом появляются плюсы в виде отсутствия лишней связности (coupling).
Другое исключение: вариант CQRS, когда команда должна обрабатывать и большой поток данных, и потом стартовать массовую обработку накопленных данных. Для первого гораздо удобнее иметь много маленьких легковесных сервисов и балансировать нагрузку количеством нод. Для второго — иметь одну‑две мощных ноды с большим количеством ресурсов. Если у вас есть доступ к FaaS, то все это вообще можно сделать, как лямбды. В таких условиях можно и смириться с недостатками разделения. А можно не мириться и остаться в одном репозитории и модуле, а проблему с нагрузкой, если уж очень надо, попробовать решить другими способами. Тут уж решение должна принимать команда, исходя из недостатков того или иного способа.
Дублирование кода/функционала
Второй код‑смелл, который на архитектурном уровне тоже норм.
Это очень интересный момент, который чаще всего является источником архитектурных проблем. Если для единой кодовой базы дублирование кода и, тем более, функционала — безусловный грех, то в архитектурном контексте все уже далеко не так просто.
Представим, например, что у одной команды есть сервис, который умеет взаимодействовать с другой системой. А теперь вам надо сделать такое же взаимодействие уже с другой командой. Вы можете реализовать взаимодействие с системой напрямую, задублировав, по сути, интеграционный код. Или же вы можете делегировать интеграцию сервису, который уже умеет в это взаимодействие.
И если вы выберете вариант избежать дублирования, то, в итоге, вы реализовали ту же самую интеграцию, но с другим контрактом. То есть технически дублирования нет, но вот логически еще как: coupling никуда не делся. Если что‑то потребуется поменять во взаимодействии, то придется все равно менять в 3 модулях.
Однако при этом у вашего решения есть и другие проблемы:
ваш сервис привязан к сервису‑посреднику, а ваша команда без особой на то причины отныне зависит от другой, что, как мы уже надежно знаем, признак плохой архитектуры;
теперь у вас во взаимодействии появилась новая точка отказа, которая может ломать ваш функционал;
если у посредника что‑то ломается, связанное только с вами, то удачи вам уговорить их что‑то срочно починить;
вместо одного контракта взаимодействия, у вас теперь два — с посредником и с системой.
И, на самом деле, это не 4 отдельные проблемы, а одна и та же. И имя ей — coupling.
А если речь не про интеграцию? Да то же самое. Если самим сделать пару дней или, там, неделю, то лучше уж сделать своё, а не завязываться на другую команду. Очень часто желание переиспользовать написанное решение приводит к зависимости одной команды от другой. И эта зависимость — тот самый coupling. Причем не хороший, который cohesion, а тот плохой, который «потрогал тут, сломалось там».
Экономя свои силы, команда передает ответственность за сроки поставки и работоспособность необходимого ей функционала на откуп другой команде. При этом другая команда, являясь поставщиком переиспользуемого кода, нередко превращается в бутылочное горлышко, которое тормозит весь поток работ нескольких команд.
Про такие бутылочные горлышки и посредников подробнее чуть ниже, когда речь зайдет о настоящих признаках coupling‑а.
Так и что? Нужно позволять командам дублировать код тут и там? Ответ тут однозначный — да. У команд должно быть право дублировать и копировать решения других команд, а выбор дублировать или нет должен оставаться за ними.
Особенно, если речь идет про интеграционный код: про клиентов, паблишеров, листенеров, консьюмеров и провайдеров. Ну, право, нет ничего плохого в том, что у одного апи будет больше одного потребителя. Напротив, это абсолютно нормально. Ниже я даже чуть подробнее напишу, почему команды должны иметь полное право на дублирование любого кода.
Можно ли сократить количество дублирования? Разумеется! Например, некоторые компании обязывают поставщиков API разрабатывать клиентские библиотеки. И для некоторых других задач корпоративные библиотеки — шикарное решение. Особенно, если логика сложная и требующая длительной разработки. Но нельзя ни в коем случае команды принуждать к их использованию: библиотека должна созреть до того уровня, когда команды выбирают её сознательно, предпочитая иным альтернативам.
Про исключения. Иногда особо сложные домены все же требуют отдельной команды и своего сервиса. В фундаментальном труде Team Topologies такие команды называются Complicated Subsystem Team. Это шаблон для решения как раз той самой проблемы, когда одна из команд уже схватилась за какую‑то сложную предметную область, а оказалось, что эта же логика нужна и другой команде. Тогда из первой команды можно выделить команду, которая будет заниматься этой сложной подсистемой, а её заказчиками будут другие команды.
Обратите внимание, что подобное решение надо принимать аккуратно и взвешенно, а таких сложных подсистем должно быть совсем немного. Авторы Team Topologies утверждают, что соотношение линейных команд (stream‑aligned) к остальным (platform, enabling и complicated subsystem) должно быть от 1 к 7 до 1 к 10. Почему?
Во‑первых, потому что основной заказчик любого бизнеса — клиент. Поэтому как можно больше ваших команд должны быть сосредоточены на потребностях клиентов.
Во‑вторых, большое количество вспомогательных команд способствует разрастанию вашей системы в глубину, что скорее плохо, чем хорошо. Об этом чуть ниже в теме про «глубокую архитектуру».
Код-смеллы, которые не норм
Хорошо, ни дублирование, ни ориентация на размер компонента тут не работают. Теперь, когда большинство из нас уже обезоружены, посмотрим на другие код‑смеллы из книжки Фаулера и разберем их.
Признак №1. Стрельба дробью (Shotgun surgery).
Очень простая идея: если каждое изменение требует доработок сразу в нескольких командах, то это говорит о том, что между ними есть явный coupling. Это комплексная проблема и, чаще всего, она является признаком других симптомов, о которых чуть позже.

Как решать? Декомпозировать на проблемы поменьше и чинить каждую в отдельности.
Когда нормально? Когда редко стреляет. Например, при выводе из эксплуатации какого‑то контракта, нам придется смириться с неизбежными правками у всех потребителей.
Признак №2. Посредник. (Middleman)
Регулярно возникающий паттерн, который нередко можно назвать антипаттерном. По сути, ничего сложного тут нет: посредник между вами и еще кем‑то — это почти всегда головняк.

Недостатки посредников я уже описывал выше:
дополнительная точка отказа;
зависимость от воли и проблем другой команды;
вместо одной интеграции придется разработать две.
Когда посредник это нормально? Когда он прячет значительную толику когнитивной нагрузки под собой и сильно повышает уровень абстракции. Примеры хороших посредников: операционная система, средства виртуализации (докер), облачные сервисы.
Скорее реже, чем чаще хорошими посредниками становятся централизованные корпоративные системы авторизации и иные инфраструктурные инструменты. То есть, если в вашей компании есть хорошие инструменты (например, платформенные), которые прям сильно упрощают вам работу с чем‑то, то это хороший посредник. Но, если этот инструмент, сделан так, что проще решить проблему самому с помощью опенсурсных решений, то это плохой посредник.
Пара слов про дублирование, как обещал выше. Именно поэтому надо разрешать командам дублировать код и писать свои велосипеды: слишком уж часто оказывается, что платформенные решения использовать дороже, чем обычный опенсурс. Конкуренция с опенсурсом подстегивает платформенные команды на создание более качественных продуктов и уберегает их от работы над мертворожденными решениям. Вдобавок самостоятельные решения команд часто вырастают в то, во что по каким‑то причинам не смогли вырасти разработки платформенных команд.
Признак №3. Теоретическая общность. (Speculative generality)
Программистам часто хочется похожие вещи положить рядом, чтобы увеличить тот самый cohesion. Но похожесть вещей еще не значит, что они действительно связаны. Когда две вещи очень похожи и вроде бы даже про одно и то же, то это еще не повод пихать их в один модуль.

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

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

Это может быть не только посредник, но и конечный поставщик, код которого можно также порезать и раздать потребителям, а команду перекинуть на другое направление.
Когда теоретическая общность — это норм? Когда команда не перегружена и не выступает для других бутылочным горлышком.
Признак №4. Неуместная близость. (Inappropiate intimacy).
Частенько можно встретить команды‑парочки, которые почти всегда на одной волне. Если одна команда делает какую‑то фичу, то и вторая делают эту же фичу. Или точно такую же, но другую.
Иногда одна команда для другой является посредником, и тут все расписано выше. Иногда обе команды работают в параллель, и новая фича это почти всегда изменения в 2 командах.

Как решать? Объединить в одну команду, если они не слишком большие и не имеют ничего против. В противном случае, перетасовать ответственность между командами.
Когда нормально? Когда объединение команд в одну приведет лишь к теоретической общности.
Признак №5. Проходной двор (Divergent changes, в оригинальном переводе Расходящиеся изменения).
Если какой‑то сервис постоянно меняется по всевозможным, не связанным друг с другом причинам, то тут, возможно, что‑то не так. Концептуально этот признак брат‑близнец Теоретической общности, потому что это ответственность у этого сервиса получается слишком общей. Поэтому все что сказано для Теоретической общности подходит и для этой проблемы.
Признаки из заработанного бессонными ночами жизненного опыта
Вот до этого места в статье я старался добросовестно опираться на ключевые в нашей индустрии источники и отсебятину добавлял дозированно. Но, в отличие от всего вышеприведенного текста, тут я вынужден опубликовать то, что в википедии пренебрежительно называется «оригинальное исследование».
То есть, несмотря на все свое знакомство с кучей первоисточников, я за годы своего опыта пока так и не наткнулся ни на что похожее. Поэтому приведу лишь небольшую выжимку из собственного опыта работы. Список небольшой, но, наверное, будет пополняться.
Признак №6. Глубокая архитектура (многослойность).
Большинство системных архитектур, которые мы видим, достаточно глубокие. Это означает, что между точкой входа (например, UI) и местом, где завершается обработка, есть целая куча модулей посередине. Этот признак ходит парой вместе с кучей вышеупомянутых: это и стрельба дробью, и посредник, и проходной двор. Иначе говоря, глубокая система имеет вид горизонтально нарезанных слоев‑этажей, через которые вглубь архитектуры спускаются запросы и пробиваются данные.

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

В глубоких архитектурах присутствует сразу ворох проблем, характерных для «посредника», только возведенные в степень. И чем глубже архитектура, тем больше показатель этой степени.
Возьмем список проблем, характерных для «посредника»:
дополнительные точки отказа;
вместо одной интеграции придется разработать несколько;
зависимость от воли другой команды;
Классическая глубокая архитектура имеет, как правило, несколько слоев, на всем тракте, что в итоге может сказаться в увеличении числа инцидентов и времени недоступности в разы (вспоминаем Теорию вероятностей).
Другая проблема, которая всплывает в глубокой архитектуре — это взаимозависимость команд. Если вашей команде для реализации вашего функционала нужно участие еще десятка команд, которые заинтересованы в своих задачах, то я вам не завидую. По сути, здесь работает та же логика: с какой‑то частотой вашу работу будет блокировать та или иная команда по своим внутренним причинам. Чем больше команд, от которых вы зависите, тем больше времени ваша команда будет ждать, прежде чем сможет решить поставленную проблему.
Если говорить о количестве контрактов, то тут тоже накладные расходы, естественно умножаются с глубиной архитектуры. Чем глубже архитектура и сложнее система, тем больше ваша организация занимается просто реализацией взаимодействия друг с другом.
Давайте посмотрим, что нужно сделать для посредника‑паразита с единственной задачей: взять из одного модуля и отдать в другой. Одно условие: нужно сделать это с высокой надежностью, в нагруженной системе и с несколькими связанными между собой ресурсами, изменяющимися в разное время. Итого нам необходимо:
поддержать входной контракт.
Обеспечить его надежность (например, идемпотентность, rateLimiter) и обеспечить необходимую нагрузку.
Прихранить данные, чтобы они не потерялись в случае падения сервиса.
Обеспечить консистентность, атомарность, изоляцию при сохранении.
Обеспечить нагрузку при записи и чтении: индексы, партиционирование, чтобы со временем не было деградации при запросах.
При очень большой нагрузке — распределенные хранилища.
Поддержать выходной контракт.
Обеспечить достаточный выходной поток.
Обеспечить надежность: перепосылы, transaction outbox, circuitBreaker.
В итоге, получается, что даже просто надежный нагруженный посредник для достаточно развесистой и сложной логики изменения ресурса — само по себе сложная задача. Если сделать одного посредника между получателем и отправителем, то легко тут можем занять целую команду. Если сделать десять таких посредников, то получим десять команд. Если нагрузить каждую небольшим количеством бизнес‑логики, то будет даже казаться, что все команды имеют какую‑то свою ответственность, а архитектура — это не переливание из пустого в порожнее, а вполне себе адекватное решение. Вот так и вырастают большие неповоротливые архитектуры, которые поддерживают буквально орды программистов.

Еще одно больное место во всех этих барьерах между командой и началом потока — это, по сути, отрыв от первоисточника данных. Очень часто, это означает оторванность команды от пользователя, что никогда не хорошо.
Как решать такие проблемы? Убивать посредников и распределять ответственности между командами не горизонтальными слоями, а вертикальными срезами.
Сколько слоев должно быть в хорошей архитектуре? В идеале — один. Команды должны работать в параллель, не блокируя друг друга.

Когда «глубокая архитектура оправдана? Рано или поздно в очень больших архитектурах даже при самом аккуратном проектировании в некоторых местах некая глубина появляется. Она возникает за счет появления платформ и „сложных подсистем“ (Complicated Subsystem). Помимо этого свою лепту вносит событийный компонент, когда предметная область одних команд — это обработка вторичных данных других. Но эти проблемы харатерны для отдельных участков очень больших архитектур и при этом характеризуются очень небольшой глубиной (2, максимум 3 слоя; в идеальном мире, конечно).
Признак №7. Дирижёр
На самом деле, такие же проблемы могут возникать и в иных топологиях. То есть дело тут не столько в глубокой архитектуре, сколько именно в coupling‑связях. Хороший пример — дирижер.

Если сервис, чтобы получить все необходимые данные, вынужден собрать их с десятка команд, то все проблемы глубокой архитектуры применимы и к нему.
Как решать? Лечение в стиле «убей посредника» тут работает уже не всегда так хорошо, как хотелось бы. Порой тут ближе признак «неуместная близость» и решения из этого подхода.

Когда нормально? Когда ничего не поделаешь, ну процесс такой, реально надо. Иначе говоря, когда вы пока не догоняете, где тут спрятался «плохой coupling». Подождите пару месяцев, подумайте, возможно, что‑то придумается.
Признак №8. Бутылочное горлышко
Если вам для вашей задачи нужна помощь другой команды, а у неё свои задачи, поэтому подождите, не до вас пока; то вот он пример «бутылочного горлышка». Скорее всего, в это горлышко пытаетесь пролезть не только вы. А значит это команда стала бутылочным горлышком и для нескольких других команд тоже. В итоге, несколько команд работают со скоростью одной.

Мы уже обсуждали, что такие проблемы появляются чаще всего в случае, если мы внедрили «Посредника», пытаясь устранить дублирование.
Решение: убей посредника или устрани теоретическую общность. Примеры выше.
Когда нормально? В тех же случаях, когда и посредник — это нормально. Когда сервис прячет значительную часть когнитивной нагрузки под капотом, значительно повышая уровень абстракции.
Как правило, даже хорошие посредники очень часто бывают бутылочными горлышками, потому что у них очень много потребителей. Решение проблемы в таком случае — расширение горлышка. Проще говоря, разделить команду на несколько, грамотно поделить ответственность так, чтобы они друг другу не мешали, добрать в новые команды специалистов.
Признак №9. Общий релиз.
Если несколько команд вынуждены иметь общий релиз, чтобы выпустить функционал — это прямой признак того, что они связаны между собой. Иначе зачем бы им было ждать общего релиза, чтобы выпустить свой функционал.
Как чинить?
Решать проблемы связности команд.
Вводить механизмы обратной совместимости.
Выдавать командам их собственные механизмы поставки.
Передавать ответственность за поставку в команду.
Передавать ответственность за механизмы поставки в команду.
Да‑да, не команда девопс пишет пайпы, а сама команда себе пишет то, что ей самой нужно.
Когда нормально? Никогда. Читаем начало статьи: основа хорошей архитектуры — команда может ставиться в пром независимо ни от кого.
Признак №10 Горячая картошка.
Регулярно встречающийся признак во всей индустрии. Думаю, целая куча очень известных и больших бизнесов сейчас себя узнают.
Это архитектурное решение, когда в систему попадает какой‑то основной объект (заказ, документ, заявка), после чего модули его начинают отпинывать друг в друга.

У меня есть подозрение, что такие архитектуры популярны, потому что архитекторы — это всегда бывшие программисты. И им просто привычно мыслить любую обработку, как последовательность шагов.
В целом, это просто один из вариантов глубокой архитектуры, при этом чуть ли не любое изменение в такой системе — стрельба дробью.
Для таких систем характерны все проблемы глубоких архитектур, плюс очень часто всплывает еще одна: распределенные вычисления и конкуренция.
Например:
Что делать, если у нас отмена?
Что делать, если мы прошли половину модулей, но кто‑то оказался быстрее и занял разделяемый ресурс?
То, что вы легко разрулили бы в одном модуле на одном хранилище, внезапно начинает требовать от вас решать CAP‑теорему и прочие хитрости делать.
Как лечить? Как уже сказано в признаке Глубокая архитектура, делить ответственности вертикальными срезами. Там же и иллюстрация
Когда нормально? Трудно себе представить ситуацию, где это решение будет лучше вертикальных слайсов.
Вместо заключения - техника безопасности
Хорошая архитектура - это роскошь для элиты
Первый вывод, который следует из самого начала статьи: архитектура важна, важнее любой другой технической практики. Но для успеха часто совершенно не обязательна. В мире полно успешных компаний, где под капотом просто швах. Поэтому, если вы планируете какие‑то большие архитектурные изменения, делайте их очень аккуратно, чтобы не сломать существующий бизнес. Поймите меня правильно, я технический перфекционист, у меня глаз дергается от плохого кода и проблемных архитектур, но хорошая архитектура сама собой деньги бизнесу не заработает.
Если у вас лапки
Практически каждый, кто прочитает эту статью, сможет найти место в своей архитектуре, которое исправить не получится ну никак. Даже если вся ваша архитектура — кусок пахучей субстанции, и из‑за этого страдает вся компания, то с этим далеко не всегда можно хоть что‑то сделать.
Большая часть советов большей части читателей окажется бесполезной, потому что у них нет никакой возможности повлиять на самое главное — на распределение ответственностей.
Тем более спровоцировать реструктуризацию организации. Поэтому, в каком‑то смысле, эта статья кому‑то даже навредила: вот работал человек, и все было у него хорошо, а теперь он оказался в кошмарном клубке антипаттернов, и ничего не может с этим сделать. Что делать? Читаем пункт 1 техники безопасности, расслабляемся и получаем удовольствие от жизни и работы.
У самурая должен быть не только путь
Отдавайте себе отчёт в том, зачем вы меняете архитектуру.
Иногда переделки архитектуры необходимы для обеспечения взрывного роста компании, как это было у Амазона, Гугла, Нетфликса и Фейсбука. И тут всегда важно помнить, что вы — не Гугл.
Порой такие вмешательства необходимы для элементарного продолжения хотя бы какой‑то разработки, как это было у HP LaserJet. Тогда масштабные инвестиции в изменение архитектуры и процессов себя окупили. Но если у вас и так как‑то катится, то рекомендую довольствоваться правилом бойскаута: оставляйте архитектуру лучше после каждого её изменения.
Бумага все стерпит
Все, что описано сверху — это общие идеи и размышления на тему. В своей работе вам безусловно следует исходить из обстановки на земле. Если вы видите, что у вас «горячая картошка», то это не значит, что надо срочно все переписывать, а команды перетасовывать. Это даже не значит, что это плохая архитектура. Легко может оказаться, что даже в такой архитектуре вы все равно имеете практически полную независимость команд друг от друга.
АМ/КГ
Все вышеописанное — это микс из исследований, профильной литературы и личного опыта в произвольной авторской пропорции. Так что воспринимайте это со здравой долей скепсиса. Если вы считаете, что автор не прав или что‑то не учел, можете смело написать об этом в своей крутой статье по архитектуре.
Меня зовут Саша Раковский. Работаю техлидом в расчетном центре одного из крупнейших банков РФ, где ежедневно проводятся миллионы платежей, а ошибка может стоить банку очень дорого. Законченный фанат экстремального программирования, а значит и DDD, TDD, и вот этого всего. Штуки редкие, крутые, так мало кто умеет, для этого я здесь — делюсь опытом. Если стало интересно, добро пожаловать в мой блог.