Системное мышление на практике: переход от монолита к микрофронтендам и обратно
Меня зовут Олег, я уже 20 лет работаю в ИТ и в основном на Enterprise-проектах. Сейчас работаю в Альфа-Банке на проекте Альфа-Онлайн и хочу поделиться своим видением управления сложностью на больших проектах.
Мой опыт работы с десятком комплексных решений показал, что системное мышление не просто важно, — оно жизненно необходимо для успешного ведения сложных проектов. Применение системного подхода позволяет не только увидеть «большую картину» во всей её полноте, но и предвидеть потенциальные проблемы, что является решающим фактором для эффективного управления проектами.
Цель данной статьи — демонстрация использования системного подхода при решении задач, которые стоят перед современными архитекторами и проектными командами. Мы рассмотрим важность стратегического мышления, оценки последствий решений на несколько шагов вперёд, и как важно избегать поспешных заключений, которые могут обернуться долгосрочными проблемами.
Проблемы белых людей
Наше монолитное приложение со временем стало занимать всё больше времени на деплой и становилось всё сложнее. Мы решили разделить его на микрофронтенды, учитывая, что у нас уже была микросервисная инфраструктура для бекенда. Однако, возникло больше 15-ти общих библиотек, от которых зависело большинство приложений. Очевидно, что со внешними зависимостями, которых у каждого модуля свой собственный набор версий, тоже не всё просто. Обновить палитру цветов, например, на всём сайте за один проход оказалось невероятно сложным.
Концептуальная схема микрофронтендного приложения. Библиотеки обозначены книжечками, а конечные приложения — значком атома. Большой атом — хостовое приложение. Изменения в ui-ках могут влиять на хост, поэтому линии зависимости от него тоже проведены.
Эмерджентные свойства
Модули, связанные между собой, процессы и люди образуют систему — совокупность элементов, которые действуют вместе как единое целое, выполняя определенную функцию, недоступную составляющим её элементам или массиву элементов.
Структура, взаимосвязи и состав порождают дополнительные функции и свойства, которых нет у отдельных частей. Это свойство называется эмерджентным.
Например, эмерджентные свойства софтверной системы — это архитектурные атрибуты качества.
Архитектурные атрибуты качества
Человек, выполняющий роль Архитектора ПО исполняет ключевую функцию в управлении этими свойствами, чтобы обеспечить надежность, производительность и удобство сопровождения системы.
Например, одно из эмерджентных свойств софтверных систем на которые я смотрю при ревизии архитектуры — ортогональность — это когда изменения в одной части системы не должны приводить к изменениям в другой части. Это упрощает поддержку потому что не нужно держать в голове всю систему, и позволяет избежать непредсказуемых эффектов.
Методы обеспечения ортогональности: использование интерфейсов, паттернов проектирования (например, Dependency Injection), модульности и слабой связанности, избавление от barrel-файлов в библиотеках, и файлов, часто приводящих к конфликтам при мерже.
Важный признак системы: если убрать, добавить или заменить элемент в системе, эмерджентное свойство меняется. Иначе, это не система, а нагромождение элементов.
Ещё системам свойственна обратная связь (feedback loop — ускорение или торможение изменений со временем). Выделяют четыре типа обратной связи:
Усиливающая: увеличивает изменения в системе (как карточный домик или ряд домино).
Уравновешивающая: стабилизирует систему (клапан для спуска пара).
Упреждающая усиливающая: подготовка к изменениям, усиливающим изменения системы.
Упреждающая уравновешивающая: подготовка к изменениям, стабилизирующая систему.
Понимание наличия обратной связи растянутой во времени помогает вносить изменения в систему и адаптироваться к новым условиям.
В нашем случае любое изменение функциональности микрофронтендного модуля может затронуть общие библиотеки, а это влияет на множество других приложений. Это приводит к увеличению циклов обратной связи и пересечению бизнесовых и технических изменений. Простые шаблонные изменения, такие, как добавление .dockerignore во все приложения может занять от трёх месяцев из-за необходимости распределять задачи по продуктовым командам.
Сложность системы также определяется типом связей, количеством состояний элементов и обратной связью.
Есть два типа сложности связей:
Количественная сложность — простые связи, с которыми легко работать объединяя элементы системы в группы.
Динамическая сложность — элементы могут быть в разных состояниях и конфигурациях взаимоотношений. Например, взаимодействие двух объектов в невесомости гораздо предсказуемее, чем трёх (отсылка к нерешаемой в общем случае задаче трех тел).
Связи в любой системе — это основа. Сами элементы могут меняться, но именно связи между элементами являются наиболее важными.
Визуализация динамической сложности. Техническое улучшение библиотеки пересекается с изменениями в приложении, и как изменения в приложении влияют на другие приложения через изменения в используемой ими библиотеки компонентов.
Чем сложнее и запутаннее связи, тем больше усилий необходимо, чтобы поддерживать систему в рабочем состоянии и развивать её.
Поиск решения
В течение века со времени основания системного подхода как отдельной дисциплины было изобретено множество методов описания систем и процессов для работы с динамической сложностью. Например, PDCA и IDEF0.
Модель PDCA помогает структурировать процессы через планирование, выполнение, проверку и корректировку. Это позволяет координировать работу разных людей и отделов, улучшая взаимодействие и эффективность. Показывать организационным структурам как их работа влияет на работу других и видеть динамику системы в целом. Собственно, в рамках неё и было введено понятие обратной связи, о котором мы говорили выше.
Динамика систем. Эдвардс Деминг (1950).
IDEF0 — язык моделирования процессов, разработанный ВВС США. Он позволяет легко декомпозировать системы и процессы на составляющие части:
Декомпозиция процесса с помощью IDEF0
Процесс: основной элемент диаграммы.
Вход: необходимые ресурсы для процесса.
Выход: результат процесса.
Механизмы: оборудование и люди, участвующие в процессе.
Управление: управляющие элементы процесса (чем это управляется).
Ещё пример применения IDEF0 — путь кода через ревью и проверки качества.
Если долго всматриваться в эту диаграмму, то можно заметить, что:
Есть закольцованность — на первый этап очень часто можем вернуться.
У нас порядка 5 систем только статической проверки кода, не считая динамических проверок. Это автоматические, а есть ещё и ручное ревью разработика, дизайнера и даже аналитика иногда.
В диаграмме нет стандартов, то есть они есть в компании, но на схеме их нет — в какой момент они должны влиять не совсем понятно.
Нотацию IDEF0 можно дорабатывать для демонстрации каких-то важных деталей процесса. Например, после того как я провёл postmortem нескольких задач, которые были оценены в 2 недели, но PR-ы висели 9 месяцев, я нарисовал такую вот черновую схему для инициирования пересмотра и изменений в производственном процессе, который приводил к году Time to Market вместо месяца.
Черновой рисунок на Remarkable в самолёте. Простите за почерк.
Также я приложил анализ истории PR-ов за год в котором видно что 90% из них — технические и залетают за неделю, но бизнесовые фичи ждали мерджа по месяцу, в среднем, потому что циклов, возвращающих на первый этап ревью, было много. В одном — целых 8 раз код проходил эти круги ада до тестировщика и обратно.
Результатом стал пересмотр и разработка нового производственного процесса Альфа-Онлайн.
Как видно, даже черновой рисунок может вскрыть системные проблемы и помочь прокоммуницировать их.
В процессе рассмотрения одного из вариантов решения — перехода на монорепозиторий (монорепозиторий позволил бы делать атомарные коммиты сразу и в библиотеку и в приложения, что упростило бы управление изменениями) — учитывая примеры других проектов, было исписано много бумаги и написано много писем для прояснения «белых пятен». Выяснилось, что это решение вызвало бы новые проблемы.
Проблемы с Bitbucket. Мы столкнулись с рядом проблем при использовании Bitbucket: отсутствие CODEOWNERS, старая версия на слабом железе, неудобство просмотра diff, проблемы с тестированием и код-ревью, а также случайный выбор ревьюеров и низкое качество кода. В монорепозитории эти проблемы бы умножились на 50.
Проблемы с переносом кода. Перенос кода в монорепозиторий требовал пересмотра и изменений процессов в десятках команд, доработки инфраструктуры и платформы. При том, что процессы толком не описаны.
Предположительный фидбек луп от такого изменения был бы катастрофическим, поэтому мы начали готовить окружение к этому изменению (упреждающие изменения).
Для решения этих проблем мы применили системный подход, который помогает предсказывать будущее и учитывать растянутые во времени причины и следствия. На убеждение лидов ушли месяцы, но время было потрачено не зря.
Влияние убеждений на изменения в системах
Любая система отражает мышление её создателей. Когда мы предлагаем изменения, нам нужно объяснить людям, почему их подходы стоит пересмотреть.
Однако, ограничивающие убеждения, такие как «У меня всё хорошо», могут сильно препятствовать изменениям. Признаки этих убеждений включают быстрые выводы, обвинение других, обобщения на основе единичных случаев, и злоупотребление авторитетными словами вроде «правильно», «нужно», «должны».
Такие убеждения указывают на отсутствие критического и системного мышления. В противоположность им, поддерживающие убеждения способствуют развитию: это стремление к новым ментальным моделям, отсутствие страха перед неопределенностью, внимание к противоречиям и причинно-следственным связям.
Я никогда не был джуном или мидлом. Начав как айтишник, я быстро стал сеньором, без наставника, который бы указывал, как правильно. Это заставило меня исследовать свои предположения, учитывать различные факторы и искать причинно-следственные связи. Такой подход избавил меня от страха перед неопределенностью — я привык работать в условиях хаоса и неопределённости, и мне хватает 10% ясности, чтобы развивать понимание системы, изучать, искать связи и двигаться вперёд шаг за шагом.
В итоге
Мы доработали Bitbucket.
Обосновали необходимость дополнительных ресурсов.
Настроили метрики для понимания состояния системы.
Учли особенности проектов и разработали особый ноу-хау подход работы с монорепозиторием микрофронтендов.
Внедрили в проектах Yarn 4 и CODEOWNERS.
Ужесточили релизный цикл.
Пересмотрели и доработали производственный процесс.
И улучшили онбординг новых сотрудников.
Но работы ещё много. Предстоит путь из такого состояния:
Далеко не полный список репозиториев проекта. Тут вперемешку и либы и приложения, и чёрти-что ещё.
В такое:
«Монорепа мечты» в которой 1 гигабайт зависимостей на всех
Выводы
Избегайте динамических связей. Чем меньше состояний объекта и возможных интерфейсов он реализует, тем лучше.
Ослабляйте связи и меняйте убеждения людей для внесения изменений в сложную систему. Чтобы изменить систему, нужно ослабить связи. Сначала выясните, на что это повлияет, ослабьте связи, возможно, напишите более универсальные интерфейсы и уже потом внесите изменения.
Неустанно ищите лучшие ментальные модели. Проверяйте реальность. Если есть какое-то убеждение, его нужно обязательно проверить.
Используйте подходящие нотации. IDEF0 не единственная нотация, но мне она нравится больше всех. Какие-то части системы лучше описывать с помощью BPMN, но для функционального моделирования нотации IDEF, по моему скромному мнению, самые классные.
Разбавляйте изменения периодами стабильности. Внося изменения в изменяющуюся систему, вы только увеличиваете динамическую сложность.
Занимайте метапозицию, чтобы видеть картину в целом и динамику во времени. Периодически нужно выходить из системы и смотреть на неё снаружи, чтобы понять, как она работает и принимать объективные решения.
Ещё немного выводов
Вот некоторые выводы из моего личного опыта, подкрепленные терминологией системного подхода.
Не работают стандарты свыше — aka «black-box-оптимизация». Представьте, что у вас есть какой-то процесс с входящими и исходящими элементами, стрелочками управления и материалами. Если мы не докомпозируем этот процесс, а просто меняем входящие и исходящие элементы, но не изучаем внутренние процессы, это может помочь, а может и не помочь. Сначала нужно разобраться в сути процесса.
В книге «Пять пороков команды» Патрика Ленсиони утверждается, что стандарты должны создаваться в конфликтах. Если произошел конфликт, чтобы его разрешить, мы фиксируем достигнутые договоренности. Стандарт становится таковым только тогда, когда его начинают требовать все от всех, иначе система не будет работать.
Не работает увеличение управленческого ресурса. Система — это гибкая структура, которую можно изменять. Управление важно, иначе система быстро превратится в хаос. Управление это и есть гарант наличия упреждающей и стабилизирующей обратной связи. Увеличение управленческого ресурса может выглядеть как сдавливание системы, и рано или поздно она либо впадет в стагнацию, либо кардинально изменится, если готова к изменениям.
Решение вымышленных проблем масштабирования. На одном из проектов внедрялось всё для решения проблем масштабирования — kubernetes, docker, балансировка нагрузки, но MongoDB использовалась как реляционная база данных. Это вызывало переполнение памяти и падения на проде раз в сутки. Фокус был направлен совсем не туда.
Не работает коллективная собственность. В экономике есть такое явление, как collective property overuse (перегрузка коллективной собственности). Если у деревни один огород, он рано или поздно перестанет плодоносить из-за чрезмерного использования.
В нашем случае каждый микрофронтенд имеет свою команду, включая владельца продукта, системного аналитика, тестировщика, разработчиков и дизайнера. Но общие библиотеки и виджеты находятся в коллективной собственности. И регулярно в (общем) чате спрашивают, что там с виджетами, потому что у виджетов есть общие зависимости, и если их обновить, некоторые виджеты ломаются.
Системную проблему нельзя решать локально. Для решения системной проблемы нужен системный подход.
Спустя несколько лет внедрения BDUI-технологий, команды разработки столкнулись с тем, что при реализации функциональности команды искали обходные решения проблем с отступами, и впоследствии это вызвало системную проблему — мы не знаем на что повлияет изменение отступа компонента. Мы вынуждены проводить полный регресс на трёх платформах чтобы выяснить что могло сломать изменение одного универсального компонента (замечали баги вёрстки в нашем мобильном приложении?).
Бонус: упражнение на системное мышление
Возьмем любой предмет и осмыслим его предназначение, потенциальные изменения и влияние этих изменений на функциональность и удобство использования. Рассмотрим, например, кружку и мобильный телефон.
Кружка
Основное предназначение кружки — хранение и потребление напитков.
Кружка должна быть удобной для держания и питья, а также устойчивой к высоким температурам, если используется для горячих напитков.
Изменения в дизайне кружки, такие как увеличение или уменьшение объема, могут влиять на удобство использования. Например, большая кружка может быть удобной для тех, кто любит пить много чая или кофе за раз, но она может быть тяжелее и менее удобной для маленьких рук.
Материалы, из которых изготовлена кружка, также важны: керамика и стекло хорошо удерживают тепло, но могут быть хрупкими, тогда как металлические кружки прочные, но могут нагреваться слишком сильно.
Особенность данной конкретной кружки ещё и в том, что это входной подарок в одну очень крутую компанию. Демонстрируя эту кружку, можно испытывать удовольствие от гордости за принадлежность к этой компании, что добавляет эмоциональную ценность и уникальность предмету.
Просто кружка
Производство кружки включает в себя выбор материалов (керамика, стекло, металл), формовку, обжиг (для керамики), а также нанесение покрытия или рисунка. В использовании кружка должна быть удобной для питья, устойчивой к повреждениям и легко моющейся.
Мобильный телефон
Основное предназначение мобильного телефона — предоставлять возможность связи, включая голосовые звонки, текстовые сообщения и доступ к интернету.
Увеличение размера экрана улучшает удобство использования для просмотра мультимедиа, чтения и работы с приложениями, но может ухудшить портативность и удобство использования одной рукой.
Удаление разъема для наушников позволяет сделать устройство тоньше и улучшить водонепроницаемость, но вызывает неудобства для пользователей проводных наушников, требуя использования переходников или беспроводных наушников.
Добавление функции беспроводной зарядки делает зарядку удобнее, но увеличивает стоимость устройства и требует совместимой зарядной станции.
Улучшение камеры повышает качество фотографий и видео, что важно для пользователей социальных сетей и медиа, но увеличивает стоимость и энергопотребление устройства.
Увеличение емкости батареи продлевает время работы без подзарядки, улучшая удобство использования, но может увеличить вес и толщину устройства.
Производство мобильного телефона включает использование металла, стекла, пластика и литий-ионных батарей, а также процессы сборки компонентов, пайки микросхем и тестирования на производительность и качество. В использовании телефон предназначен для голосовых звонков, текстовых сообщений и доступа к интернету, а также для фотосъемки, видеосъемки, использования приложений, навигации и платежей.