Архитектура программного обеспечения — это набор значимых решений о системе, которые дорого менять: как она делится на модули, где хранит данные, с какими системами интегрируется и как развёртывается. Каждое такое решение — компромисс между атрибутами качества: надёжностью, безопасностью, производительностью, стоимостью и простотой изменений. Решения показывают диаграммами (например, по C4-модели) и записывают с причинами в ADR — короткие документы в репозитории.
После материала вы сможете
- отличить архитектурное решение от детали реализации по тому, сколько стоит его изменить
- связать решения проекта — границы модулей, хранение данных, интеграции, развёртывание — с атрибутами качества и назвать цену каждого
- нарисовать диаграммы контекста и контейнеров по C4-модели с подписанными связями
- оформить решение в виде ADR по шаблону Майкла Найгарда и заменить устаревшее решение новым ADR
Что знать заранее
Проверено на
C4 model (c4model.com)ADR: Michael Nygard format (2011)markdownlint-cli2 0.23.3 (markdownlint 0.41.1)
Сверено с документацией: 11 октября 2026 г.
Содержание
Архитектура — это решения, которые дорого менять
Проект состоит из сотен решений: как назвать функцию, какую библиотеку дат взять, где хранить данные, как приложения общаются между собой. Большинство из них пересматривается за час. Несколько — нет: смена базы данных, разделение приложения на сервисы или новый способ входа затрагивают весь код и все данные. Именно эти решения и называют архитектурой.
Мартин Фаулер в колонке «Who Needs an Architect?» (IEEE Software, 2003) разбирает два определения Ральфа Джонсона. Первое: архитектура — это общее понимание устройства системы, которое разделяют ведущие разработчики. Второе: это решения, которые хотелось бы принять правильно в самом начале проекта. Люди хотят принять их рано, потому что считают их трудными для изменения, — отсюда рабочее определение «то, что считают трудным для изменения» и итог Фаулера: архитектура — это про важное, что бы это ни было.
Практичный фильтр дал Майкл Найгард в статье об ADR (2011): архитектурно значимы решения, которые влияют на структуру системы, её нефункциональные характеристики, зависимости, интерфейсы или технику построения.
Одно приложение или несколько сервисов
- Архитектурное?
- Да
- Почему
- Меняет сборку, развёртывание, мониторинг и границы транзакций
PostgreSQL или документная база для записей
- Архитектурное?
- Да
- Почему
- Переписываются запросы и модель данных, нужна миграция
Как узнаём об оплате: вебхук или периодический опрос
- Архитектурное?
- Да
- Почему
- Интерфейс с внешней системой, влияет на надёжность
Где хранятся сессии и как проверяется вход
- Архитектурное?
- Да
- Почему
- Безопасность, затрагивает каждый запрос
Какая библиотека форматирует даты
- Архитектурное?
- Обычно нет
- Почему
- Меняется в одном месте, если спрятана за своей функцией
Имена переменных и стиль кода
- Архитектурное?
- Нет
- Почему
- Меняется рефакторингом и линтером
Атрибуты качества: что должна выдерживать система
Функциональные требования говорят, что система делает: клиент записывается к мастеру, администратор переносит запись. Атрибуты качества — их ещё называют нефункциональными требованиями — говорят, как она это делает: насколько надёжно, безопасно, быстро и за какие деньги. Архитектура отвечает в основном на них: одну и ту же функцию «записаться» можно построить десятком способов с разными свойствами.
Удобная рамка — пять столпов Azure Well-Architected Framework от Microsoft: надёжность, безопасность, оптимизация затрат, операционное совершенство и эффективность производительности. Рамка написана для облака, но её вопросы подходят любой системе. Добавим шестой атрибут — изменяемость, то есть сколько стоит внести изменение. Фаулер подчёркивает, что высокое внутреннее качество не замедляет, а ускоряет поставку функций: в коде меньше «мусора», который мешает его понять.
Надёжность
- Вопрос к системе
- Что будет при сбое одной из частей?
- Пример требования для сервиса записи
- Запись сохраняется, даже если Telegram не отвечает; напоминание уйдёт позже
Безопасность
- Вопрос к системе
- Кто к чему имеет доступ?
- Пример требования для сервиса записи
- Мастер видит только свои записи; телефоны клиентов не попадают в логи
Производительность
- Вопрос к системе
- Сколько ждёт пользователь под нагрузкой?
- Пример требования для сервиса записи
- Свободные слоты открываются без задержек в вечерний пик записи
Затраты
- Вопрос к системе
- Сколько стоит работа системы?
- Пример требования для сервиса записи
- Нет серверов, которые простаивают ради редких пиков
Эксплуатация
- Вопрос к системе
- Как узнать о проблеме и выпустить исправление?
- Пример требования для сервиса записи
- Ошибки видны в мониторинге, релиз откатывается одной командой
Изменяемость
- Вопрос к системе
- Сколько стоит новая функция?
- Пример требования для сервиса записи
- Новый способ оплаты добавляется без правок в модуле записей
Компромиссы вместо «лучших практик»
Атрибуты качества конфликтуют: улучшая один, почти всегда платишь другим. В Well-Architected Framework у каждого столпа есть отдельная страница компромиссов. Несколько примеров из раздела о производительности:
- Кэш ускоряет ответы, но чем дольше живёт копия, тем вероятнее, что она отражает устаревшее состояние: закэшированное решение об авторизации может пускать пользователя, которого уже заблокировали
- Компоненты ради скорости — шина сообщений, балансировщик, кэш — увеличивают поверхность атаки и стоимость, а их самих тоже нужно делать надёжными
- Шардирование и денормализация ускоряют работу с большими данными, но усложняют согласованность и рутинные операции вроде резервного копирования
- Плотное размещение нескольких приложений на одной платформе экономит ресурсы, но расширяет радиус поражения при сбое
Вопрос «как правильно?»
- Ищет универсальный ответ
- Копирует архитектуру крупных компаний
- Добавляет компоненты «на вырост»
- Не говорит, когда решение пересмотреть
Вопрос «чем платим?»
- Называет атрибут, который важнее сейчас
- Перечисляет, что станет хуже
- Сверяется с размером команды и реальной нагрузкой
- Фиксирует условие, при котором решение пересматривают
Поэтому фраза «так лучше» в архитектуре звучит подозрительно. Рабочая формулировка — «для нашей нагрузки и нашей команды мы выбираем X ради атрибута A и принимаем цену B». Именно её записывают в ADR, о котором речь ниже.
Модули и границы
Модуль — часть системы с одной зоной ответственности и явным публичным интерфейсом. Граница модуля отвечает на вопрос, что снаружи можно использовать, а что является его внутренним устройством. Для сервиса записи естественные модули совпадают с языком бизнеса: «Записи», «Расписание», «Уведомления», «Оплата».
- Модуль владеет своими данными: таблицы платежей читает и меняет только «Оплата»
- Соседи обращаются к нему через публичный интерфейс — функции, события или API, а не через внутренние файлы
- Зависимости между модулями направлены в одну сторону и не образуют цикл
- Изменение внутри модуля не требует правок в соседних
- Модуль можно протестировать, подменив соседей заглушками
Граница не обязана проходить по сети. Модули могут жить в одном приложении — это модульный монолит — или быть отдельными сервисами. Фаулер в заметке «MonolithFirst» пишет, что почти все успешные истории микросервисов начинались с монолита, который разросся и был разделён, а даже опытным архитекторам трудно правильно провести границы в начале проекта. Отсюда порядок: сначала границы внутри кода, затем, если появится причина, — сетевые.
Внутри backend-модуля код обычно делят ещё и на слои: контроллер принимает HTTP, сервис держит бизнес-правила, репозиторий работает с базой. Это следующий уровень приближения — его разбирает урок о слоях backend-приложения.
Нарисовать систему: C4-модель
Архитектуру нужно уметь показать: новому разработчику, заказчику, себе через полгода. C4-модель Саймона Брауна предлагает рисовать систему как карту с масштабом — четыре уровня приближения, от общего вида до кода. Модель не зависит от нотации и инструмента: подойдут и маркерная доска, и любой редактор диаграмм.
- 01Контекст (System Context)Система целиком, её пользователи и внешние системы. Понятна и не техническим людям
- 02Контейнеры (Container)Приложения и хранилища данных внутри системы, технологии и протоколы между ними
- 03Компоненты (Component)Модули внутри одного контейнера и их ответственность
- 04Код (Code)Классы и функции. Лучше генерировать в IDE, чем рисовать вручную
Сайт c4model.com рекомендует диаграммы контекста и контейнеров всем командам. Диаграмму компонентов советует рисовать, только если она добавляет ценность, а для долгоживущей документации — генерировать автоматически. Диаграмму кода для долгоживущей документации не рекомендует: IDE строит её по запросу. Начнём с контекста сервиса записи.
- 01Клиент салона
- 02Сервис онлайн-записи
- 03Telegram Bot API
Уровень контейнеров раскрывает «Сервис онлайн-записи»: из каких запускаемых частей он состоит и как они общаются.
- 01Браузер клиента
- 02Веб-приложение (Next.js)
- 03API записи (Node.js)
- 04База данных (PostgreSQL)
Веб-приложение
- Тип и технология
- Контейнер: Next.js
- Ответственность
- Экраны записи для клиента и расписание для администратора
- Связи
- Вызывает API записи (JSON через HTTPS)
API записи
- Тип и технология
- Контейнер: Node.js
- Ответственность
- Модули «Записи», «Расписание», «Уведомления», «Оплата»; фоновая отправка напоминаний
- Связи
- Читает и пишет данные (SQL); отправляет напоминания в Telegram Bot API (HTTPS)
База данных
- Тип и технология
- Контейнер: PostgreSQL
- Ответственность
- Записи, слоты, клиенты, очередь напоминаний
- Связи
- —
Telegram Bot API
- Тип и технология
- Внешняя система
- Ответственность
- Доставка напоминаний клиенту
- Связи
- —
- У диаграммы есть заголовок с типом и областью: «Диаграмма контейнеров: сервис онлайн-записи»
- Есть легенда: что означают формы, цвета, линии и стрелки
- У каждого элемента указан тип, у контейнеров и компонентов — технология
- Каждая стрелка однонаправленная и подписана конкретно: не «использует», а «отправляет напоминания через HTTPS»
ADR: записывать решения и их причины
Диаграмма показывает, что построено, но не объясняет почему. Через год новый разработчик увидит монолит и спросит, почему не микросервисы. Майкл Найгард описал два плохих исхода: слепо принять старое решение, не понимая его, или слепо поменять, сломав то, ради чего его принимали. Его ответ — Architecture Decision Record (ADR), короткая запись об одном архитектурном решении и его причинах.
ADR хранят в репозитории рядом с кодом, в Markdown, и нумеруют по порядку; номера не переиспользуют. Объём — одна-две страницы: по словам Найгарда, большие документы никто не читает и никто не обновляет. Формат состоит из пяти частей.
Название
Номер и короткая фраза о решении: «ADR 0002: Модульный монолит для сервиса записи».
Контекст
Силы, которые действуют на решение: технические, организационные, сроки, команда. Нейтральный язык, только факты.
Решение
Ответ на эти силы полными предложениями в активном залоге: «Мы будем…».
Статус
«Предложено», пока команда не согласовала, затем «Принято». Позже — «Устарело» или «Заменено» со ссылкой на новый ADR.
Последствия
Каким станет контекст после решения: что упростится и что усложнится. Все последствия, а не только положительные.
# ADR NNNN: Короткая фраза о решении
## Контекст
Какие силы действуют: требования, ограничения, сроки, команда, нагрузка.
Только факты, без оценок. Какие варианты рассматривали.
## Решение
Что мы делаем. Полные предложения в активном залоге: «Мы будем…».
## Статус
Предложено | Принято | Устарело | Заменено ADR NNNN
## Последствия
Что станет проще и что станет сложнее после решения.
Все последствия, а не только положительные.# ADR 0002: Модульный монолит для сервиса записи
## Контекст
Сервис онлайн-записи в салоны: клиент выбирает услугу, мастера и время,
администратор видит расписание, клиент получает напоминание в Telegram.
Команда — три разработчика, релиз раз в неделю. Модули «Записи»,
«Расписание», «Уведомления» и «Оплата» выделены, но их границы проверены
только на одном салоне. Запись не должна теряться, если Telegram недоступен.
## Решение
Мы будем развёртывать серверную часть как одно приложение Node.js с одной
базой PostgreSQL; веб-интерфейс на Next.js остаётся отдельным приложением.
Каждый модуль лежит в своей папке, владеет своими таблицами и открывает
другим модулям только публичный интерфейс. Импорт внутренних файлов чужого
модуля запрещает линтер в CI. Напоминания отправляет фоновая задача: запись
сохраняется в базе сразу, отправка повторяется при сбое Telegram.
## Статус
Принято, 2026-10-11. Заменяет ADR 0001 «Отдельный сервис уведомлений».
## Последствия
Одна сборка и одно развёртывание: проще мониторинг, откат и локальный запуск.
Создание записи и бронирование слота остаются одной транзакцией базы данных.
Модули нельзя масштабировать и выпускать по отдельности: исправление в
«Оплате» требует релиза всего приложения. Границы держатся на дисциплине и
проверке импортов; без неё монолит станет «клубком».
Если модулю понадобится своё масштабирование или отдельная команда, мы
вынесем его в сервис и оформим это новым ADR, который заменит этот.Шаблон Найгарда — не единственный. Каталог на adr.github.io и репозиторий Джоэла Паркера Хендерсона собирают и другие: MADR с явным списком рассмотренных вариантов и их плюсов и минусов, Y-statements — решение в одном структурированном предложении, подробный шаблон Тайри и Акермана. Важнее регулярность, чем выбор шаблона. Писать ADR на каждое решение не нужно: временный обходной путь, прототип или эксперимент с ограниченным риском и ценой документа не требуют.
Эволюция: архитектура растёт вместе с продуктом
Архитектуру не утверждают один раз навсегда. Фаулер приводит письмо Джонсона: в программах нет теоретической причины, по которой что-то трудно изменить. Любую отдельную часть можно сделать лёгкой для изменения, но каждая такая часть немного усложняет систему, а если сделать гибким всё, система станет очень сложной — сложность же и делает программы трудными для изменения. Поэтому одна из главных задач архитектора, по Фаулеру, — убирать необратимость там, где она обходится дорого, а не делать гибким всё подряд.
- Откладывайте дорогие решения, пока не хватит информации, но не дольше: «разберёмся потом» — тоже решение, и его стоит записать
- Прячьте то, что вероятно изменится, за своим интерфейсом: платёжного провайдера, канал уведомлений, файловое хранилище
- Пересматривайте ADR, когда меняются условия из раздела «Контекст»: выросла команда или нагрузка, появилось требование регулятора
- Обновляйте диаграммы контекста и контейнеров вместе с кодом — они меняются редко, поэтому поддерживать их дёшево
Дальше в направлении — принципы, по которым проводят границы внутри кода: SOLID, а затем паттерны проектирования, которые реализуют эти границы на практике.
Как проверено2 · пройденосверено 11 октября 2026 г.
- запущено, работает
docs/adr/template.md (RU и EN) — шаблон ADR в формате Найгарда
markdownlint-cli2 0.23.3 (markdownlint 0.41.1), настройки по умолчанию: 0 issues; структура разделов сверена со статьёй Найгарда
- запущено, работает
docs/adr/0002-modulnyj-monolit.md / 0002-modular-monolith.md — пример ADR
markdownlint-cli2 0.23.3, настройки по умолчанию: 0 issues; код в статье вставлен из проверенных файлов скриптом сборки
«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.
Источники
- Martin Fowler — Software Architecture Guide
- Martin Fowler — Who Needs an Architect? (IEEE Software, 2003)
- Michael Nygard — Documenting Architecture Decisions
- Architectural Decision Records (adr.github.io)
- The C4 model for visualising software architecture
- Azure Well-Architected Framework — Performance Efficiency tradeoffs
