Search
Write a publication
Pull to refresh

Архитектурные принципы

Level of difficultyEasy
Reading time6 min
Views6.2K
Original author: Ruslan Taghiyev

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

Итак, без долгих предисловий:

1. Всегда учитывайте контекст

Не создавайте “Сферических коней в вакууме”

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

Примеры:

  • Можно привести много аргументов, почему Rust лучше Java. Но важен контекст. Какие конкретные преимущества Rust актуальны для нашей задачи? Что, если у нас среднестатистическое веб-приложение и при этом вся экосистема построена на Java? Сколько плюсов будет в этом случае?

  • Микросервисная архитектура имеет множество достоинств. Но в нашем случае — действительно ли они нам нужны? Что, если у нас одна команда, низкие нагрузки и высокие требования к транзакционности? Не будут ли микросервисы в этом случае только усложнением и оверхедом?

2. Решайте только реальные проблемы

Не решайте несуществующих проблем (YAGNI)

Не оптимизируйте преждевременно. Как говорил Дональд Кнут, “Преждевременная оптимизация — это корень всех бед”.

Любое решение или архитектура должны быть обоснованы. Мы не решаем проблем, которых у нас нет, будь то усложнение архитектуры ради гипотетических выгод или преждевременная оптимизация работающего кода. Решения должны рождаться из реальных потребностей, а не из желания «сделать красиво» или «как у больших компаний», иначе это просто трата ресурсов.

Это не означает игнорировать будущее. Стратегическое планирование на основе известных или высоковероятных изменений — это не преждевременная оптимизация, а необходимая дальновидность. Ключ к оптимальной системе — это баланс между «не делать лишнего» и «быть готовыми к росту»(про это подробнее в 4 принципе).

Примеры:

  • Решение несуществующей проблемы:

    • Да, Redis может ускорить доступ к данным. Но если база данных и так отвечает за миллисекунды, и нет перегрузки — зачем кэш? Его нужно будет настраивать, инвалидировать, следить за консистентностью. Не создавайте себе лишнюю точку отказа без необходимости.

    • «А вдруг потом у нас появятся графовые зависимости, давайте сразу возьмём Neo4j» — нет. Строить архитектуру на основе возможных, но пока несуществующих требований — ошибка. Делайте просто, пока нет необходимости в сложном.

  • Преждевременная оптимизация:

    У нас есть простая страница, на которой отображаются 100 записей. Запрос к базе выполняется за 20 мс, нагрузка — минимальная, изменений в объёме или модели данных не предвидится. В такой ситуации нет смысла добавлять индексы, выносить агрегации в отдельные сервисы, делать кеширование или писать ручной SQL вместо ORM. Это преждевременная оптимизация, которая усложняет код и увеличивает время разработки.
    Но если мы знаем, что в ближайшее время эта страница станет точкой высокой нагрузки — тогда стоит заранее подумать о производительности и заложить нужные механизмы.

  • Когда это правило не работает:

    Мы проектируем сложную функциональность, которая затрагивает всю архитектуру. Сейчас ей будут пользоваться только 10 человек, и может показаться, что перформанс не важен. Но при уточнении с продуктовой командой выясняется, что в течение двух лет её аудитория может вырасти до 500 тысяч пользователей. В этом случае нельзя закрывать глаза на масштабируемость и высокую доступность — нужно учитывать перформанс уже на этапе проектирования, чтобы потом не переписывать всё с нуля.

3. В приоритете - простота и поддержка (KISS)

В первую очередь архитектура должна быть простой и легко поддерживаемой. Простая система легче в понимании, быстрее разрабатывается, проще отлаживается и дешевле сопровождается. К тому же, часто простые решения оказываются и более производительными — за счёт меньшего количества слоёв, абстракций и зависимостей.

Чем сложнее система, тем легче сделать ошибку, тем выше её цена и тем сложнее обучение новых участников команды. 

Примеры:

  • Модульный монолит может звучать не так модно, как микросервисы. Но в большинстве проектов на ранних этапах он проще в разработке, отладке, деплойменте и тестировании. Не нужно настраивать сервис-дискавери, оркестраторы, авторизацию между сервисами, распределённый трейсинг и т.д.

  • Rich Domain Model, Event Sourcing, CQRS — мощные инструменты. Но нужны ли они нам? Если у вас CRUD на 10 эндпоинтов — они будут не решением, а лишним грузом. Используйте такие подходы, когда у вас действительно есть та сложность, ради которой они создавались.

  • Гетерогенный технический стек может показаться привлекательным выбором, но в реальности он очень скоро превратится в сложно поддерживаемый зоопарк. В то же время гомогенный стек намного проще и лучше поддерживается — по крайней мере до момента, пока вам не начинают требоваться узкоспециализированные фичи или определённые преимущества других решений.

4. Взвешивайте компромиссы (трейд-оффы) и держите баланс.

Архитектура — это всегда искусство баланса. Серебрянных пуль не существует. Каждое решение влечёт за собой последствия: мы что-то выигрываем и что-то теряем. Хорошая архитектура — это не «лучшее решение», а наиболее уместное в конкретном контексте, с учётом целей, ограничений и рисков. Про это даже целая книга написана.

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

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

Примеры:

  • CAP-теорема

    В распределённых системах нельзя одновременно обеспечить консистентность, доступность и устойчивость к разделению сети. Нужно выбирать два из трёх. Например, Cassandra делает ставку на доступность и устойчивость, жертвуя строгой консистентностью. А PostgreSQL в режиме репликации может обеспечить консистентность, но при этом становится менее доступным.

  • Скорость разработки vs долгосрочная поддержка

    Быстрое написание кода без тестов и правильного дизайна может сэкономить время в моменте, но приведёт к техническому долгу в будущем.

5. Проектируйте стратегически для создания эволюционирующей архитектуры

Не создавайте архитектуру «на века» — у вас всё равно не получится. Требования, технологии и рынок постоянно меняются, поэтому единственная жизнеспособная архитектура — та, что способна эволюционировать. Она должна не только отвечать текущим требованиям, но и быть расширяемой для поддержки будущих изменений.

Ключ к такой эволюции — это стратегический подход к проектированию, а не тактический.

  • Тактическое решение — это решение задачи "в лоб": сделать ровно то, что требуют, не думая о дизайне и последствиях.

  • Стратегическое решение — это инвестиция времени в анализ и дизайн, чтобы система была модульной, расширяемой и готовой к изменениям.

Как утверждает Джон Остерхаут в своей книге, тактический подход в средне- и долгосрочной перспективе не ускоряет, а наоборот — тормозит развитие. Стратегический подход, напротив, — это инвестиция в скорость будущих изменений. 

При этом важен баланс: не нужно каждый раз тратить +100% времени на начальный дизайн если в этом нет явной необходимости. Но дисциплинированное выделение хотя-бы 10–30% времени окупается уже через несколько месяцев.

Примеры:

  • У вас есть набор текущих требований.

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

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

  • Исключение:

    Вам нужно срочно написать PoC для заказчика или чтобы проверить идею. В этом случае тактический подход приемлем, но только с условием, что PoC будет переписан и не превратится в MVP.

По сути, правильный архитектурный дизайн — это и есть стратегическое решение проблемы.

Заключение

Существует множество различных принципов и даже архитектурных фреймворков. Я мог бы включить сюда гораздо больше. Но, на мой взгляд, перечисленные здесь принципы являются наиболее фундаментальными и универсальными. Остальные либо вытекают из них, либо являются их естественным продолжением.

Tags:
Hubs:
+8
Comments15

Articles