Сегодня я бы хотел представить вам архитектурные принципы, которыми я руководствуюсь при создании приложений. Я считаю, что эти принципы применимы к подавляющему большинству приложений, за редкими исключениями. И даже несмотря на то, что каждый из них является фундаментальным, я в своей практике раз за разом замечаю, как люди напрочь про них забывают. И так как я не видел, чтобы они были где-либо представлены в едином коротком виде, я решил сделать это тут.
Итак, без долгих предисловий:
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.
По сути, правильный архитектурный дизайн — это и есть стратегическое решение проблемы.
Заключение
Существует множество различных принципов и даже архитектурных фреймворков. Я мог бы включить сюда гораздо больше. Но, на мой взгляд, перечисленные здесь принципы являются наиболее фундаментальными и универсальными. Остальные либо вытекают из них, либо являются их естественным продолжением.