№1 Принимайте решения как можно позже
Я не имею в виду откладывать каждое повседневное решение до последнего момента. Речь идёт о крупных архитектурных решениях.
Вот несколько примеров решений, для которых стоит применять этот принцип:
-
Выбор типа базы данных
-
Выбор общей архитектуры решения (например, микросервисы)
-
Определение границ микросервисов
-
Оптимизация под высокую нагрузку и производительность
-
Стратегии автоматизированного сквозного тестирования
-
Введение сложных абстракций и архитектурных паттернов
Когда мы откладываем такие решения до последнего возможного момента, у нас накапливается больше фактов и понимания, что позволяет принять наилучшее возможное решение.
Часто реальные проблемы пользователей становятся понятны только на этапе активной разработки или после получения первых отзывов.
Важно начинать с простого и гибкого решения, потому что, скорее всего, нам придётся менять курс, как только появится обратная связь от пользователей.
Можно ли обойтись просто хранением данных в памяти или использованием JSON-файлов на старте?
Действительно ли вам нужна высокодоступная, автоматически масштабируемая, суперпроизводительная и элегантная система, когда у вас всего несколько пользователей?
**Детализированное проектирование ** — один из главных убийц проектов!
Существует слишком много историй, когда «архитекторы PowerPoint» предлагали сложные схемы с самого начала, а проект быстро превращался в громоздкий и непригодный для поддержки кошмар.
Нужно ли избегать сложных паттернов и технологий?
Конечно нет!
Но использовать их стоит в нужный момент, когда есть факты, подтверждающие необходимость таких решений.
Этот принцип применим даже к написанию кода.
Я — большой сторонник использования известных паттернов, но даже самые опытные инженеры не пишут идеально абстрагированный код с первой попытки.
Хорошо написанный код — это всегда процесс:
-
Напишите простой код, чтобы решить задачу (обычно получается процедурный код с большими методами и классами).
-
Напишите тесты, чтобы убедиться, что код работает как надо (можно начать с тестов, если следуете TDD).
-
Сократите дублирование кода, оптимизируйте и выделяйте функции, классы, паттерны.
-
Используйте тесты, чтобы убедиться, что оптимизация не сломала поведение кода.
Если оптимизировать или абстрагировать слишком рано, можно быстро зайти в тупик и потерять кучу времени на распутывание проблем.
Гибкость
Мы понимаем, что вряд ли всё получится идеально с первого раза.
Поэтому в начале проекта важно закладывать гибкость, чтобы легче вносить изменения в будущем.
Есть множество приёмов для повышения гибкости.
Ограниченные контексты (Bounded Contexts)
Bounded Contexts помогают разбить проблему на более простые и понятные части.
Их можно использовать не только для проектирования архитектуры, но и для организации кода внутри проекта.
Вместо того чтобы сразу бросаться в сложные решения вроде микросервисов, начните с ограниченных контекстов — их потом можно выделить в отдельные микросервисы, когда система станет более понятной и стабильной.
Модульный монолит (Modular Monolith)
Сейчас набирает популярность подход, при котором используется модульный монолит — код изолируется по модулям в рамках одного процесса, вместо того чтобы сразу разворачивать множество отдельных (микро)сервисов.
Если изначально допустить ошибку в определении границ модулей (что вполне вероятно), в монолитной архитектуре намного проще внести изменения.
Со временем, если станет видно, что некоторые модули имеют разные требования к масштабированию или производительности, можно с уверенностью вложить усилия в выделение таких модулей в отдельные микросервисы.
Event Sourcing
Event Sourcing предполагает хранение всех событий, произошедших с сущностью, в неизменяемом потоке событий (Event Stream).
События могут материализоваться в различные проекции (Projections), соответствующие конкретным требованиям к запросам.
Если для новой функции потребуется новая проекция данных, мы можем просто использовать поток событий для её генерации на основе всей истории событий.
Это очень удобно, потому что, даже если изначально модель данных была определена не совсем точно, мы всегда можем создать новые проекции без необходимости менять сами события.
№2 Качество важнее количества
Мы создаём программное обеспечение для наших пользователей и стейкхолдеров.
Им важно, чтобы продукт был доставлен вовремя — а вот как он устроен внутри, как он спроектирован и реализован, их в большинстве случаев не интересует.
В работе продуктовой команды очень легко войти в привычку как можно быстрее выпускать новые фичи, чтобы радовать пользователей, жертвуя качеством и тестированием.
К сожалению, такой подход — путь в никуда, который в долгосрочной перспективе замедлит работу.
Когда на проекте пренебрегают качеством, кодовая база начинает портиться, так как растёт технический долг.
Со временем код становится трудно поддерживать, а скорость работы команды (и её моральный дух) быстро падают.
Именно поэтому опытные архитекторы почти всегда делают выбор в пользу качества —
даже если это требует большего времени или усилий.
Задавайте вопросы командам продукта и дизайна
Команды Product и Design сами не работают с кодовой базой проекта,
поэтому последствия плохого качества они ощущают гораздо меньше.
Архитектору важно задавать им вопросы в отношении объёма новых фич и следить за тем, чтобы качество не страдало.
Есть ли у нас данные или обратная связь от пользователей, подтверждающие необходимость этих изменений?
Часто команды продукта и дизайна склонны пытаться довести до совершенства новые функции, ещё до того, как появляется реальное подтверждение, что это действительно важно для пользователей.
Задача архитектора — следить за тем, чтобы команда работала рационально и не тратила ресурсы впустую.
Лучше сначала выпустить небольшую функциональность,
которая построена гибко, удобно для поддержки и хорошо протестирована,
а затем, на основе реального использования в продакшене, постепенно её дорабатывать.
YAGNI: “You Aren’t Gonna Need It” — Тебе это не понадобится
Этот принцип обычно пропагандируют программисты:
Не пиши код, который не нужен прямо сейчас.
Если у тебя есть код, который не используется — удали его.
(У нас же есть системы контроля версий — если понадобится, всегда можно восстановить.)
Больше кода = больше сопровождения, больше тестов, больше потенциальных ошибок.
Архитекторы тоже должны активно поддерживать принцип YAGNI.
И это касается не только кода:
YAGNI применим и к фичам продукта и дизайна.
Добавление ненужных функций отнимает ценные инженерные ресурсы, создаёт дополнительную нагрузку на тестирование и поддержку, увеличивает вероятность багов и расширяет поверхность атаки для хакеров.
Покрытие тестами
Покрытие тестами (Code Coverage) — не идеальная метрика, но её измерение и отслеживание крайне важно!
Оно помогает разработчикам быть честными и заставляет принимать лучшие решения при написании кода.
Если кто-то пишет ненужный код, ему всё равно придётся написать тесты для него —
и это часто становится хорошим сигналом: “почему я вообще пишу это? Нужно ли оно на самом деле?”
Как люди, мы склонны искать лёгкие пути.
”Что случится, если я не напишу тест для одного небольшого метода?” —
но именно такие маленькие решения быстро превращаются в большую проблему.
Архитектор должен всегда настаивать на соблюдении минимальных порогов покрытия тестами, чтобы не допустить накопления технического долга.
№3 Архитектура прежде всего для команды
Многие архитекторы не понимают, что устройство команды должно быть одним из важнейших факторов при проектировании архитектуры ПО.
-
Есть ли у вашей команды опыт и компетенции, чтобы разрабатывать и поддерживать предложенную архитектуру/технологии?
-
Правильно ли устроена команда, чтобы эффективно строить предлагаемую архитектуру?
Команда напрямую влияет не только на выбор технологий и паттернов, но и на архитектуру системы в целом.
Закон Конвея
Связь между структурой команды и архитектурой объясняется Законом Конвея, который должен знать и учитывать каждый архитектор:
Любая организация, разрабатывающая систему (в широком смысле),
создаст такую структуру системы, которая будет копией коммуникационной структуры этой организации.
— Мелвин Конвей
Небольшие независимые команды естественным образом склонны строить сервисные архитектуры (на основе микросервисов).
А вот большие единые команды чаще всего создают плотно связанные монолитные архитектуры.
Существует множество команд, которые жалуются, что архитектуры на базе микросервисов — далеко не истина, как им казалось изначально.
Эти жалобы чаще всего исходят от больших команд, которые пытаются строить множество микросервисов, используя одну единую команду.
Обычно это заканчивается огромной неэффективностью и тем, что появляется так называемый распределённый монолит (Distributed Monolith).
Закон Конвея как раз объясняет, почему такой подход совершенно не работает.
Крупнейшие компании вроде Amazon никогда бы не смогли добиться своих успехов так быстро, если бы заранее не разделили свои команды и архитектуры на множество независимых микросервисов.
Приём “Инверсный Конвей” (Inverse-Conway-Maneuver)
Если мы хотим разрабатывать программное обеспечение максимально эффективно,
нам нужно связывать проектирование архитектуры с организацией команд.
Уважая закон Конвея, мы можем заранее организовать команды таким образом,
чтобы их структура отражала желаемую архитектуру системы.
Это даёт наилучшие шансы на успех — такой подход называется Инверсный Конвей (Inverse-Conway-Maneuver).
Таким образом, дизайн команды и архитектуры не будет постоянно бороться с естественными коммуникационными потоками в организации.
Технологии и паттерны важны — но куда важнее, когда и где их применять
Понимание различных технологий и архитектурных паттернов — крайне важно, чтобы быть хорошим архитектором.
Но настоящее мастерство архитектора проявляется в том, насколько хорошо он понимает, когда и где их использовать.
Если хочешь стать действительно крутым архитектором ПО, нужно также хорошо разбираться в динамике команд, в управлении командами и участвовать в их формировании, чтобы они могли работать максимально эффективно и слаженно с предлагаемыми архитектурами систем.