Эволюционная архитектура - это такой подход к построению архитектуры, который ставит перед собой цель двигаться вперед небольшими шагами, сохраняя темп разработки.
В основе такой архитектуры лежит свойство «эволюционность», т.е. это способность программного продукта развиваться с течением времени не требуя при этом полного перестроения приложения.
Обычно эволюционность определяется относительно конкретных требований. Но может быть свойством всего приложения.
Эволюционность прекрасно уживается с другими «здоровыми» требованиями. Основной проблемой развития программного обеспечения являются «антипаттерны», эволюционная архитектура это один из вариантов решения этой проблемы. Антипаттерны порождают нездоровые требования.
Эволюционность напрямую зависит от организации процесса разработки и невозможна без организации соответствующего процесса разработки.
На уровне кода и приложения, т.е. на уровне обычных разработчиков проблемы создают архитектурные, методологические и организационные антипаттерны.
- Большой комок грязи (Big ball of mud): Система с нераспознаваемой структурой.
- God object – решение проблемы зацепления путем концентрации функций в одном
месте.
- Раздувание интерфейса (Interface bloat): Разработка интерфейса очень мощным и
очень сложным для реализации.
- Перестыковка (Re-Coupling): Процесс внедрения ненужной зависимости.
- Состояние гонки (Race hazard, Race condition): непредвиденные возможности
наступления событий в порядке, отличном от ожидаемого.
- Мышиная возня: Неоправданное создание множества мелких и абстрактных классов
для решения одной конкретной задачи более высокого уровня.
- Программирование методом копирования-вставки: копирование (и лёгкая
модификация) существующего кода вместо создания общих решений
- Дефакторинг (De-Factoring): Процесс уничтожения функциональности и
замены её документацией или организационными действиями (предписаниями)
- Золотой молоток (Golden hammer): Сильная уверенность в том, что любимое
решение универсально применимо. Название происходит от поговорки «когда
в руках молоток, все проблемы кажутся гвоздями».
- Преждевременная оптимизация (Premature optimization): Оптимизация на
этапе проектирования сегмента кода, приводящая к его усложнению или
искажению.
- Программирование методом подбора (Programming by permutation): Подход
к разработке программного обеспечения небольшими рандомными изменениями.
- Два тоннеля (или два стула): Вынесение новой функциональности в отдельное
приложение вместо расширения уже имеющегося.
- Ад зависимостей: Разрастание графа взаимных зависимостей программных продуктов
и библиотек, приводящее к сложности установки новых и удаления старых продуктов.
- Вендорный король (vendor king) – когда архитектура строится от возможностей вендора.
Основная проблема, которую нужно решить, чтобы сделать хороший софт. В каждом подходе она решается по-разному.
Как разделить приложение так, чтобы можно было двигаться вперед внося небольшие, «подъемные» изменения, с другой не упасть в антипаттерны (ад зависимостей, копи-паст и т.д.)
Грануляция – оптимально разделение на модули или компоненты. При этом грануляция отвечает на вопрос атомарности (нахождения функционально неделимых компонентов, которые порождают новую функциональность путем комбинирования атомов друг с другом). Не путать с монолитностью.
- Модули – логическое разделение
- Компоненты – физическое (компонент – это как правило класс в ООП, либо
композиция классов). Один из вариантов компонента – сервис, библиотека.
- единственная ответственность – это принцип помогает бороться с желанием
укрупнять и делать God-компоненты, но не мешает чрезмерному уменьшению
компонентов. Грануляция – это не про измельчение, а про поиск золотой
середины.
- принцип подстановки Барбары Лисков, очень часто не до конца понимается
этот принцип, поэтому использую только в простейшей форме – если можно
подставить родителя самого верхнего уровня, то все ок.
- принцип открытости/закрытости, так как часто нарушается, очень трудно
реализуется на практике, в итоге приводит к «copy/paste» или «два тоннеля»
- зацепление и связанность – два принципа, которые четко позволяют понять
оптимальный уровень грануляции приложения (на старте 2-3 связи на модуль,
иначе комбинаторная сложность убьет приложение и не позволят делать «небольшие»
изменения)
- программирование в соответствии с интерфейсом – ведение минималистичных интерфейсов,
которые на старте содержат 2-3 метода, опять же комбинаторная сложность Именно
ограничение связей и публичных методов, позволяет очень продвинуть эволюционность,
но важно понимать, что уровень грануляции проекта меняется с течением времени и развития
проекта. На поздних стадиях, минималистичным можно вполне считать интерфейсы с 5-7
методами, но при этом важно уменьшать комбинаторную сложность.
Архитектура любого приложения - это рассмотрение задачи с разных углов зрения и определение возможностей̆ софта. Дополнительные направления для анализа эволюционности (кроме уровня кода, рассмотренного выше):
- Техника/инфраструктура
- Данные
- Безопасность
- Интеграции/системы
Закон Мелвина Конвея: "Организации, которые проектируют системы, склонны воспроизводить архитектуру которая воспроизводит организационную структуру самой организации"
Если вы что-то не можете посчитать, значит вы это не можете контролировать. Значение мониторинга так же важно, как и в микросервисной архитектуре
Основа – фитнес функции, могут быть как автоматизированными, так и организационными.
Целью проектирования могут выступать любые требования, которые были получены как от заказчика, так и те которые являются следствием проектирования. Мы можем сделать фитнес функцию в виде автоматизированного теста, а можем в виде инструкции для человека, который будет вручную проверять наличие необходимых свойств у системы.
Важно чтобы фитнес функции максимально автоматизировались. Например, если мы определим требование производительности для выполнения набора операций, скажем 100мс на операцию, то мы можем написать небольшую функцию или программу, которая будет контролировать данный показатель.и будет фитнес функция.
Фитнес функции могут быть следующих видов:
- атомарные/целостные
- непрерывные/прерывающиеся
- статические/динамические
- автоматизированные/ручные - временные
По назначению фитнес функции бьются на:
- ключевые
- релевантные/нерелевантные
Это основной механизм эволюционной архитектуры. Идея в том, чтобы двигаться вперед небольшими шагами, заменяя устаревшие компоненты более новыми аналогами. При этом в какой-то момент времени могут быть доступны и старые и новые компоненты. Небольшим изменение может оставаться только до тех пор пока оно самодостаточно, не имеет лишних связей и не решает глобальных проблем. После каждого изменения должен идти этап развертывания.
Основа эволюционной архитектуры - непрерывное развертывание.
- инкрементальные изменения
- построение и поддержание фитнес функций
- поддержание модульности и управление связями
Решение:
- Декомпозиция на составные задачи
- Прозрачность до реализации последней задачи (идея «финальной точки»)
Мы должны ждать когда будут реализованы изменения в зависимых модулях.
Решение:
- в редких случаях – ждать, если речь идет о ядре или библиотечных функциях
- реактивность
- выделение API и поддержание обратной совместимости
- условная компиляция/сборка
Разные команды хотят похожую функциональность, но есть небольшая разница.
Решение:
- Дополнительная грануляция, пересмотр атомарности.
Приложение уже сильнозацепленный неструктурированный монолит, маленькие шаги в
принципе невозможны.
Решение:
- Применение паттерна «два стула» - новую функциональность реализовывать отдельно,
старую выносить короткими итерациями. Важно! В долгосрочной перспективе это
антипаттерн, поэтому в таком состоянии долго находиться нельзя, иначе станет только
хуже.
Раздутые интерфейсы и классы порождают очень много разных цепочек
Решение:
- уменьшение вариативности