Привет! Меня зовут Азамат, я backend-разработчик в Циане. В работе мне часто приходится пересматривать архитектуру компонентов или проектировать её с нуля. Со временем у меня накопились подходы и наблюдения, которыми хочу поделиться.

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

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

Терминология

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

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

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

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

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

Чтобы проектировать эффективно, важно договориться о терминах внутри команды — это помогает быстрее понимать друг друга.

С чего начинается проектирование

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

Перепроектирование в процессе работы — это нормально. Это и есть рефакторинг. Для него не нужны отдельные проекты и согласования, достаточно применять хорошие практики, которые описал Мартин Фаулер в книге «Рефакторинг».

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

Если вы видите существенный риск для дальнейшей разработки — это сигнал задуматься об архитектуре. В книге Design It! это состояние описано очень точно: «тянущее ощущение в животе» (глава 4, Let Risk Be Your Guide).

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

Вот несколько красных флагов, которые могут указывать на проблемы в архитектуре:

Высокие время или стоимость разработки:

  • Постоянные конфликты при слиянии веток.

  • Слишком много правок ради маленькой фичи.

  • Код сложно читать и долго в него погружаться.

  • Разработчики дублируют решения вместо переиспользования.

Высокий time-to-market для фич:

  • Долгий деплой.

  • Долгий прогон обязательных тестов.

  • Слишком много коммуникаций между командами даже на простых задачах.

Много багов (не всегда про архитектуру, но могут быть косвенные признаки):

  • Тесты сложно писать, тяжело поддерживать, они плохо читаются.

  • Даже при хорошем покрытии тесты не ловят баги.

  • В коде много неочевидных и избыточных связей.

Проблемы с производительностью. Чаще это связано с выбором инструментов (БД, алгоритмы, настройки), но иногда архитектура тоже мешает. Например, если вам нужны ACID-транзакции, разделение на два микросервиса сильно усложнит жизнь.

Описание проблемы

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

Сначала нужно определить, кто зависит от этой системы. Их ещё называют стэйкхолдерами. Обычно это:

  • Разработчики, которым уже неудобно работать или которым предстоит с ней работать.

  • Пользователи или их представители: продуктовые и проектные менеджеры, тимлиды, разработчики из других команд, специалисты, которые знают реальные боли клиентов.

  • Все остальные, кто завязан на систему или может повлиять на её развитие.

С этими людьми важно собрать информацию о текущих болях. Разработчики часто подскажут, где именно неудобно. Менеджеры помогут понять пользовательские сценарии, ограничения по времени и качеству (например, SLO), а также будущие планы, которые повлияют на архитектуру.

Полезно выяснить:

  • Какие задачи в системе встречаются чаще всего?

  • Какие части кода часто меняются, а какие — почти не трогают?

  • В каком направлении будет развиваться продукт и система?

Также важно зафиксировать сценарии использования системы. Кто и как с ней взаимодействует? Это могут быть не только люди, но и другие сервисы. Описание ключевых сценариев поможет проверить, насколько новая архитектура поддерживает реальные кейсы.

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

Обычно архитектурное проектирование — это перепроектирование уже существующей системы. Проектирование с нуля встречается реже. Даже если текущая архитектура далека от идеала, полезно собрать её верхнеуровневую схему. Это поможет увидеть дополнительные проблемы и станет опорной точкой для планирования перехода на новую архитектуру.

Если система сильно запутана, имеет смысл начать с локального рефакторинга — он поможет прояснить текущее поведение и упростит дальнейший анализ.

Что влияет на архитектуру

Когда я проектирую архитектуру, я всегда держу перед глазами список факторов, которые стоит учесть. Во многом я опираюсь на книгу Design It! (глава 5), но дополнил её своими наблюдениями.

Первое — ограничения. Они бывают технические (например, язык, операционная система, СУБД) и бизнесовые (например, бюджет, сроки). Важно отличать реальные ограничения от тех, которые вы себе надумали, чтобы не загнать проект в ненужные рамки.

Второе — функциональные требования. Это конкретный функционал, который система должна поддерживать.

Третье — качественные атрибуты. Это свойства, которые сильно влияют на архитектуру:

  • Во время разработки: изменяемость, тестируемость, переиспользуемость, скорость вывода фич (time-to-market).

  • Во время эксплуатации: доступность, надёжность, производительность, масштабируемость, безопасность.

  • Дополнительно: простота, поддерживаемость, управляемость, обучаемость.

Также на архитектуру влияет организационная структура. По закону Конвея, архитектура часто повторяет, как устроены команды и как они взаимодействуют — даже если вы этого не планируете.

Важно учитывать и цели бизнеса: куда движется компания, какие проекты планируются, как система будет развиваться.

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

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

Составление вариантов архитектуры

Когда мы думаем об архитектуре, у нас почти всегда уже есть в голове какое-то предварительное решение. Например, если для добавления фичи приходится править кучу микросервисов и из-за этого растёт time-to-market, первая мысль — может, стоит их объединить? Но тут же всплывает новая проблема: один из этих сервисов используют другие системы. Если объединить всё в один, другие системы начнут тянуть в себя лишние зависимости, и общая сложность вырастет.

Так и строится процесс: от проблемы — к решению, от решения — к новым проблемам. Мы перебираем варианты и ищем тот, который максимально снимает боль или оставляет только те проблемы, с которыми можно спокойно жить. Важно честно проверять плюсы и минусы каждого варианта.

Не нужно пытаться перебрать все архитектурные варианты. Вариантов всегда бесконечно много. Фокусируйтесь на тех, что реально решают вашу текущую проблему. Вопрос «Почему ты не вынес эту логику в отдельный микросервис?» сам по себе не имеет смысла. Правильный вопрос: «Почему для решения конкретной проблемы ты не вынес эту логику в отдельный микросервис?». Если это поможет — рассмотрите такой вариант. Если нет — спокойно идите дальше.

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

Принципы проектирования

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

Один из главных вопросов: когда стоит объединять компоненты, а когда — разделять? Особенно это актуально в микросервисной архитектуре. Хочется дробить, чтобы быстрее деплоить, но излишняя детализация часто приводит к усложнению.

Когда стоит разделять компоненты:

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

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

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

А зависит от Б, хотя мог бы зависеть только от Б1
А зависит от Б, хотя мог бы зависеть только от Б1
  • Ещё признак, который усиливает прошлый, это если от Б1 зависит несколько компонентов, или потенциально будут от него зависеть. Т.е. его хотят отдельно переиспользовать в других частях системы. Б1 явно кандидат на выделение. Схема ниже:

Б1 кандидат на отделение
Б1 кандидат на отделение
  • Часть знания дублируется в других компонентах. Скорее всего это та часть, которую, если выделить, то она будет переиспользована.

  • Внутри компонента перемешаны часто меняющиеся и редко меняющиеся части. Если выделить «горячую» часть, можно ускорить её разработку.

Когда наоборот стоит объединять:

  • Для выполнения проекта часто приходится менять несколько компонентов и деплоить их по отдельности. Объединение поможет сократить количество деплоев и упростить тестирование.

  • Между компонентами высокая связанность, которую сложно разорвать. Обычно это признак, что это один домен, который зря разделили.

Архитектура — это баланс. Иногда выгодно разделять, иногда — объединять. Баланс со временем меняется: бизнес меняет приоритеты, команды растут, система развивается. Архитектуру приходится пересматривать, и это нормально.

Не стоит забывать про закон Конвея: архитектура почти всегда повторяет структуру команд, даже если это не запланировано. В идеале — команды и архитектура должны быть выстроены друг под друга. Но на практике это не всегда получается, особенно если оргструктура нестабильна или неудобна.

DDD и чистая (луковичная) архитектура

Паттерны DDD и чистая (луковичная) архитектура отлично работают там, где в системе действительно сложная бизнес-логика. Важно: речь именно про сложность предметной области, а не про технически сложную реализацию.

Эти подходы особенно полезны, когда нет жёстких требований к высокой производительности и нет необходимости экономить на железе любой ценой.

Чистая архитектура добавляет много слоёв и абстракций. В системах с простой логикой это может привести к избыточной сложности: вы потратите время на поддержку слоёв, которые вам не нужны. Иногда это даже мешает — например, при работе с ACID-транзакциями или при оптимизациях.

Если сложна только техническая реализация, но бизнес-логика простая (например, сложные SQL-запросы, работа с памятью, оптимизация сетевых вызовов), DDD вряд ли принесёт пользу.

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

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

Устойчивые и неустойчивые компоненты

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

  • Если компонент почти ни от кого не зависит, но на него завязано много других — это устойчивый компонент.

  • Если компонент сам зависит от кучи всего, но на него почти никто не ссылается — это неустойчивый компонент.

Важно: часто меняющиеся компоненты лучше делать неустойчивыми. То есть пусть они зависят от других, а не наоборот. А те части системы, которые трогают редко (например, доменная логика, API-контракты, базовые библиотеки), наоборот, стоит делать устойчивыми и максимально изолированными.

Пример устойчивого и неустойчивого компонента
Пример устойчивого и неустойчивого компонента

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

В DDD этот принцип активно используется: доменную область стараются изолировать, сделать независимой и стабильной. Боб Мартин подробно писал об этом в «Чистой архитектуре».

Сохранение вариантов использования и их разнообразия

Один из важных принципов: максимально долго откладывать архитектурные решения, которые ограничивают варианты использования системы. Почему? Потому что реальность всегда меняется. Бизнес может сменить приоритеты, требования могут переписать, команда может вырасти или сократиться. Если вы заранее забетонируете архитектуру, можете сильно ограничить себя в будущем.

На старте лучше оставлять побольше гибкости — пока не станет понятно, что ограничения действительно оправданы.

Например, вы хотите внедрить event-driven архитектуру и сразу строить её на RabbitMQ. Но, возможно, стоит подождать. Можно начать с локальной событийной шины внутри одного процесса — это проще и дешевле. А вдруг через полгода появится требование проводить несколько операций атомарно в одной транзакции? С RabbitMQ вам придётся реализовывать распределённые транзакции, например через паттерн saga. Это дорого и сложно. В локальной архитектуре этой проблемы бы просто не было.

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

Суть простая: не стоит усложнять архитектуру раньше времени. Оставляйте себе запас для манёвра и принимайте необратимые решения только тогда, когда они действительно необходимы.

Вывод

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

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

Если вам интересно глубже разобраться в проектировании, вот книги, которые сильно помогли мне и стали основой для этой статьи:

  • Роберт Мартин — Чистая архитектура

  • Michael Keeling — Design It!: From Programmer to Software Architect

  • Мартин Фаулер — Рефакторинг: улучшение существующего кода

  • John Ousterhout — A Philosophy of Software Design (о ней я даже писал заметки на Хабре)

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

Комментарии (7)


  1. Dhwtj
    01.07.2025 10:42

    Архитектура — это всё, что критично для долговременного успеха системы.

    Техническая архитектура — это всё, что критично для долгосрочного успеха системы на уровне технологий

    Поэтому, начинать надо всё же с понимания желаемых архитектурных свойств и параметров проекта, а не с этих ваших компонентов

    1. Производительность

    2. Масштабируемость

    3. Надежность

    4. Доступность

    5. Сопровождаемость

    6. Безопасность

    7. Расширяемость

    8. Тестируемость

    9. Наблюдаемость

    10. Стоимость


    1. garipovazamat Автор
      01.07.2025 10:42

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

      Вы пишите

      начинать надо всё же с понимания желаемых архитектурных свойств

      Но как понять какие свойства желаемые? Тут и приходится разбираться, а какие цели у бизнеса, какие проекты ещё могут возникнуть ближайшее время. Это я как раз и описываю в главе "Описание проблемы".
      Другой вопрос, а когда вообще стоит задаться вопросом полного перепроектирования архитектуры? Не будем же мы при необходимости например добавления новой фичи этим заниматься каждый раз. Поэтому я описал это раньше в главе "С чего начинается проектирование" и мне показалось логичным начать именно с этого.


      1. Dhwtj
        01.07.2025 10:42

        Проект и продукт это совсем разные вещи. И я говорю продукт=система


  1. Dhwtj
    01.07.2025 10:42

    Инфоцыганство ака консалтинг надоело уже. Живые примеры давай!


    1. garipovazamat Автор
      01.07.2025 10:42

      Живые примеры не работают, когда собеседник заранее решил, что это — "инфоцыганство". Уважайте чужой труд — это хотя бы минимально прилично. А вы как отличаете ценную информацию от "инфоцыганства"?


      1. Dhwtj
        01.07.2025 10:42

        Фаулер это инфоцыган или нет?

        Он писал книги в период когда уже давно не записался практикой


        1. garipovazamat Автор
          01.07.2025 10:42

          Для меня Фаулер очень авторитетный автор, если вы называете инфоцыганом, то скорее всего вы его просто не поняли. По крайней мере его книга Рефакторинг дала мне много пользы. В любом случае, стоит с критикой относиться к любому автору, и не следовать слепо всем советам.

          Если вы с чем то не согласны с фаулером в его книге Рефакторинг, дайте конкретику.