Делить или нет: forking и метрики
Юрий начинает с блок-схемы главы: сначала решить, разумен ли монолит, потом понять, декомпозируем ли код, и выбрать между tactical forking и component-based decomposition. Его смущает порядок: метрики считаются по компонентам, а вопрос «есть ли компоненты» задаётся после. Александр читает схему иначе: сначала смотришь, как код разложен по namespace и пакетам и насколько всё перевязано; если кодовую базу можно привести в порядок — выделяешь компоненты и выносишь, если нет — форкаешь копию и, как Буратино из бревна, вырезаешь лишнее. Tactical forking он раньше не рассматривал как вариант: из одного плохого куска кода получаются два, устаревший код никуда не девается, а общая функциональность в двух копиях расходится, — это решение от безысходности, когда двум командам нужно раздать половинки. Зато для быстрой проверки продуктовой гипотезы форк сработал: Илья вспоминает, как копию приложения задеплоили отдельно, погоняли полгода и похоронили.
Александр рассказывает историю из привлечения шестилетней давности: публичное фронтовое приложение — монолит с пятьюдесятью с лишним разработчиками и релизом раз в две-три недели, а ему нужно было быстро перевести на него основные продукты. Он поднял отдельную инфраструктуру и отдельный релизный цикл для своего куска кода внутри монолита, делал два-три быстрых релиза, потом вливал ломающие изменения из общего мастера с полным регрессом, и так, пока нужные компоненты не пересадили в новый фронтенд, — монолит после этого умер. Метрики для вопроса «декомпозируем ли код» — абстрактность, нестабильность и расстояние от главной последовательности — Роберт Мартин придумал в 1994 году, ещё до Java. Юрий сомневается в абстрактности как мере расширяемости: в Python и Go абстрактных сущностей почти нет, в функциональных языках тем более; Вова возражает примером внешних систем, которые в банке меняются постоянно, — интерфейсы позволяют подменять реализацию, не переписывая код.
Нестабильность и зоны боли
Александр разбирает принцип инверсии зависимостей на мобильном банке 2019 года: логика лежала по слоям, конкретные реализации без интерфейсов, и когда единую команду разделили на четыре десятка продуктовых, оказалось, что команда билетов в ресторан завязана на сигнатуры платежей, платежи доработали под бизнес — и всё начало ломаться. Выделение интерфейсов помогло, хотя удобство контрактов между модулями никто не проектировал. Нестабильность — отношение исходящих зависимостей ко всем; Юрий путается в афферентных и эфферентных, и Александр советует, как Uncle Bob, звать их fan-in и fan-out. Если от компонента никто не зависит, нестабильность равна единице и внутри можно творить что угодно; если зависят все, он прикован цепями: в мобильном банке это кредитки, счета и платежи, которых хотят остальные сорок команд, и им тяжело дышать, а новый продукт без потребителей движется быстро и считает себя молодцом.
Отсюда две зоны на графике абстрактность–нестабильность. Зона боли — конкретика без абстракций, от которой зависят все: кишки наружу, поменять ничего нельзя, иначе придут с «платежи не работают». Зона бесполезности — интерфейсы, которые никто не использует. Александр когда-то подрядил гуру Java-сообщества проанализировать код мобильного банка этими метриками: выяснилось, что направления зависимостей местами не те, некоторые модули сидят в зоне боли, и из большой кодовой базы, которую глазами не пробежать, такие инсайты достать реально. Илья подытоживает: метрики описывают, как код нарезан сейчас, и если граф компонентов полный, как в микросервисной сети «каждый с каждым», выпилить ничего не получится — тогда правая ветка схемы, форк. Александр сравнивает: нормально распиленное приложение — кубики, из которых сын складывает слова, а форк — две кучи кинетического песка в разных углах комнаты.
Шесть шагов декомпозиции
Вторая глава называет шаги паттернами, хотя, шутит Илья, это алгоритм; Александру нравится, что каждый шаг объясняется через «зачем» и снабжается fitness functions, чтобы через два месяца всё не скатилось обратно. Шаг первый — найти и измерить компоненты: посчитать statements на компонент; у героев книги reporting занимает 30% кода, и его делят на финансовый, прочий и общие функции; функции контроля — не появляются новые namespace, ни один компонент не превышает доли от общего числа statements и не выходит за два стандартных отклонения. Александр замечает, что это не автоматизация pipeline, а способ держать архитектора в курсе: сработала — приходит и требует обоснования. Шаг второй — собрать общие доменные компоненты вроде notification, разбросанной по модулям; функция — поиск одинаковых имён и кода. Шаг третий — сплющить компоненты: классы лежат только в листовых namespace, а родительский — это поддомен; Юрию не нравится, что это лишает структуры внутри компонента, Илья предлагает считать компонентом первые три сегмента имени.
Шаг четвёртый — определить зависимости между компонентами: авторы шутят про градации сложности от мяча для гольфа до самолёта, а функции запрещают лишние зависимости на конкретный компонент. Александр спорит с идеей полностью несвязанных компонентов как идеала: система — это элементы плюс связи, и без взаимодействия нет эмерджентного поведения, как нет команды из людей, которые не общаются. Общий код выносят в библиотеки или отдельный сервис, и здесь клуб делает крюк: Александр вспоминает, что защитный слой из книги Влада можно реализовать и библиотекой, и сервисом перед потребителем, Юрий — что сбор логов в Kubernetes делают сайдкаром без правки приложений, а Илья предупреждает о латентности: он видел проект, где несвязанную систему запустили и страница рисовалась девять минут. Шаг пятый — сгруппировать компоненты в домены, по сути переименовать по конвенции; шестой — вынести в сервисы, самый короткий в главе, хотя именно в нём вся инфраструктура. Александр напоминает урок Влада — не пилить, пока непонятна бизнес-логика — и рассказывает про конфигурационный сервис для фронта, где новости инвестиций и страницы привлечения жили в одном домене отказа. Перевода книги нет — читать в оригинале, хотя, по общему мнению, воды в нём много.
Что стоит унести с собой
- 01Сначала решить, разумен ли монолит и декомпозируем ли код; tactical forking — решение от безысходности или способ быстро проверить гипотезу, а не путь к чистой архитектуре.
- 02Метрики Мартина — абстрактность, нестабильность, расстояние от главной последовательности — показывают зону боли, где от конкретики зависят все, и зону бесполезности, где интерфейсы никому не нужны.
- 03Component-based decomposition идёт шестью шагами — измерить компоненты, собрать общие, сплющить, определить зависимости, сгруппировать в домены, вынести в сервисы — с fitness functions, которые держат архитектора в курсе.
- 04Общий код можно вынести в библиотеку, сервис или сайдкар, но отдельный сервис резко повышает латентность; пилить стоит только там, где уже понятна бизнес-логика и болит связанность.