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

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

Следующим пунктом идет вопрос, а чем эволюционная архитектура может помочь читателям статьи. Основные моменты отмечены ниже.

А теперь, когда мы поняли какой это полезный зверь “эволюционная архитектура”, кажется пришла пора понять а что это такое:)

Ну и по традиции мы начнем с определения … сначала определения архитектуры. Ниже представлены два разных определения, первое было дано Гради Бучем 15 лет назад и акцентирует внимание на важных решениях, которые сложно/дорого изменить. А вот второе определение было дано Филиппом Крачтеном на вручении SEI Software Architecture Award 2020 и оно акцентирует внимание на границах и траектории развития системы. Второе определение лучше укладывается в контекст этой статьи.


Но кажется, что стоит отдельно обсудить, а что значит слово эволюционная в заглавии нашей статьи.

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

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

Давайте рассмотрим каждую часть отдельно и начнем с инкрементальных изменений.

В современном Agile мире инкрементальность изменений являются стандартом де-юре:) Обычно это подается под соусом Lean Startup, где предлагается быстро тестировать бизнес-гипотезы, получать результаты экспериментов и принимать решения. Концепция звучит хорошо, но на уровне разработки такие бесконтрольные итерации часто превращают структуру ваших приложений в спагетти, которое может проявляться как на уровне исходного кода, так и взаимодействия отдельно развернутых сервисов посредством публичных API:)

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

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

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

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

Отдельно стоит рассказать про апгрейд старых добрых нефункциональных требований и как они связаны с указанными выше архитектурными характеристиками.
Изначально в разработке программного обеспечения речь часто шла по функциональные и нефункциональные требования. Первые приносили продакты или бизнес аналитики, а вот вторые оставались за инженерной командой (системные аналитики, архитекторы, разработчики). В силу того, что функциональные требования были прямым заказом продакта, а нефункциональные требования изначально им не заказывались, то и внимание им уделялось не так много. Лейтмотивом такого отношения являлась фраза “нефункциональные требования я не заказывал, в отличие от функциональных”.
Инженерным командам надо было менять это отношение, а для этого лучше всего подходит новый нейминг. В итоге, значительная часть нефункциональных требований стала атрибутами качества. Суть в том, что теперь в случае перекоса внимания продакта на функциональные требования можно было напомнить про атрибуты качества, которые помогают созданным фичам не просто как-то работать, а работать с тем уровнем качества, что требовался.
Финальной трансформацией на этом пути стало название архитектурные характеристики, многие из которых на английском языке оканчиваются на ilities
, например, high availability
, maintainability
, auditability
, usability
и так далее. Суть в том, что эти нефункциональные требования или атрибуты качества напрямую влияют на архитектуру системы и ее характеристики. Да и звучит солидно:)
Теперь, после короткого отступления вернемся к теме fitness functions
, которые помогают с контролем того, что значения архитектурных характеристик находятся в желаемых пределах.

Комбинация индивидуальных fitness functions
формирует общесистемную fitness function
. Это могут быть комбинации разных видов тестов (юниты, контрактные, интеграционные), метрик (процессных, архитектурных), мониторинг работы систем и алертинг на важные события.
Сами fitness functions
можно разделить на следующие категории, которые отличаются степенью важности для архитектуры системы.

Для получения профита от fitness functions
нам требуется описать их , а дальше автоматизировать их запуск. Желательно прогонять их достаточно часто, идеально на каждое изменение, условно каждый pull request
в мастер ветку. Вот стандартные инструменты, которые могут помочь вам в этом сложном деле.

Интереснее всего обсудить последний пункт, в котором мы добавляем архитектурные проверки. Есть общедоступные инструменты типа ArchUnit
, которая позиционирует себя так
ArchUnit is a free, simple and extensible library for checking the architecture of your Java code using any plain Java unit test framework.
Другой инструмент — Danger
, который позиционирует себя так
Danger runs during your CI process, and gives teams the chance to automate common code review chores.
Правила для Danger
можно писать на разных языках, так что можете выбрать свой любимый.
Fitv
— это наш внутренний инструмент, который расшифровывается как Fitness Validator
. Этот инструмент мы сделали для направления архитектуры и контроля технического долга в команде, которая занималась распилом небольшого монолита на десятки сервисов. Очень хотелось, чтобы соблюдение архитектурных подходов в этих сервисах контролировалось не просто глазами технических лидов, а проверялось в CI/CD пайпланайнах соответствующих команд.
Подробнее про автоматизацию соблюдения архитектурных принципов можно посмотреть в докладе Мирослава Малкина с конференции ArchDays 2020.
А теперь перейдем к последней части эволюционной архитектуре, а именно к подходящему coupling
компонентов в системе.

Здесь первым делом надо вспомнить про принципы модульности: Cohesion
и Coupling
. Про эти принципы подробнее можно прочитать в статье “Essential Architecture #Code”.




cohesion
и coupling”Дальше нам интересны принципы организации компонентов, которые приводил дядюшка Боб (Uncle Bob) в своей книжке “Clean Architecture” (подробнее можно прочитать в моем обзоре второй части этой книги “Часть II: принципы дизайна модулей и разделения по компонентам”).

Мы рассмотрим только два принципа из шести, чтобы подробнее поговорить про стабильность и абстрактность компонентов. И начнем со стабильности. Принцип Stable Dependencies Principle
говорит про направление зависимостей, которые должны вести в сторону более устойчивых компонент. На рисунке ниже дано определение того, как считать Instability
(нестабильность) компонентов, а также показан пример того, как должен выглядеть граф зависимостей в идеальном случае. И второй принцип из этой группы — Stable Abstraction Principle
, который говорит про то, что компонент должен быть настолько же абстрактным, насколько он стабилен. На рисунке ниже представлен алгоритм расчета метрики абстрактности компонентов через количество абстрактных классов в компоненте по отношению к общему количеству классов


Ну и теперь мы можем визуализировать Stable Abstraction Principle
как показано на рисунке ниже. Суть в том, что хорошие компоненты должны группироваться в районе The Main Sequence
. Кроме того есть две особые зоны: зона боли и зона бесполезности. В зоне боли у нас идет завязка на конкретные реализации внутри стабильных компонентов. Это приводит к боли при попытке расширить систему. А в зоне бесполезности у нас есть нестабильные абстрактные компоненты, которые в принципе не имеют смысла — зачем нужны интерфейсы, которые никто не использует?

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

Для этого можно использовать триггеры потребности в эволюции из книги Team Topologies, подробнее в моем кратком обзоре этой книги в части про Evolving team interactions.

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

Теперь мы можем перейти к самим триггерам, которые перечислены ниже.

Вот какие симптомы проявляются в случае чрезмерного размера софта.

Следующие симптомы относятся к скорости поставки.

И последнее относится к дополнительной когнитивной сложности создания и изменения бизнес-сервисов из-за существующего окружении внутри компании.

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

- Книга “Evolutionary Architecture”
- Книга “Fundamentals of Software Architecture” и краткий обзор
- Книга “Clean Architecture” и обзоры 1 и 2
- Статья Сергея Баранова “Двойная петля обучения в архитектуре и фитнес-функция”
- Доклад Мирослава Малкина с конференции ArchDays 2020
- Книга «Team Topologies» — есть краткое саммари в 3 частях:
- ■ Teams as means of Delivery —
- ■ Team Topologies that work for flow —
- ■ Evolving team interactions for innovation and rapid delivery
- Статья “Essential Architecture #Code”
- Мое выступление на Techlead Conf “Как мы меняли разработку лучшего* мобильного банка под требования бизнеса”
- Мое выступление на ArchDays “Эволюция web’а tinkoff.ru”
- ArchUnit — инструмент для контроля архитектурных решений
- Danger — инструмент для контроля архитектурных решений
- Моя статья “Как прокачаться в проектировании программного обеспечения — список книг”