My_arch

0

Описание

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

README.md

Архитектура приложений, или Как сделать вид, что так и было задумано

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

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

Рекомендуется специалистам, работающим с унаследованными кодовыми базами, распределёнными системами и проектами, где ретроспектива часто подменяет планирование.


Введение

от автора

Меня зовут Николай Иванович С. Программистом я стал в далёком 1955 году, когда вместо клавиатуры был перфоратор, а за ошибку в операторе условного перехода приходилось платить сутками ожидания у машины. Тогда ещё никто не знал, что такое архитектура приложений, но все чувствовали: если к коду подходить без дисциплины — будет беда.

Я видел, как машины занимали целые залы, а потом уместились в карман. Я писал для «Минска», для ЕС ЭВМ, потом для PDP, потом для «Винды», потом для облаков, потом — в облаках. В девяностые мои коллеги уходили в ларьки и охрану, а я остался и пережил всё — и Java 1.1, и SOAP, и тот самый проект, где документация хранилась в голове у аналитика, который внезапно уехал в Гоа.

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

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

Николай Иванович С.,
инженер-программист,
ветеран систем, которые до сих пор где-то работают.
Москва, 2025.

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



Примечание

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

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

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

Николай Иванович С.
Москва, 2025.

Глава 1

Когда архитектура уже есть — но вы о ней ещё не знаете

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

Первый признак существования архитектуры — когда вы не можете изменить один компонент, не затронув при этом половину остальных. Второй признак — когда новый человек в команде задаёт один и тот же вопрос: “Почему это устроено именно так?”, — и никто не может ответить однозначно.

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

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

В этой главе мы рассмотрим:

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

Архитектура — это не то, что написано в Confluence. Это то, что удерживает ваше приложение от распада. Даже если вы об этом ещё не знаете.

Как диагностировать архитектуру, если нет документации

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

1. Начните с точек входа

Определите, где система принимает запросы извне. Это могут быть REST-эндпоинты, очереди, CLI-интерфейсы, cron-задачи, системные события. Каждая такая точка — это вход в архитектуру. Запишите их. Поймите, какие компоненты активируются и в каком порядке.

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

2. Определите места хранения состояния

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

Если вы видите, что один модуль обращается к трём БД и двум Redis-экземплярам — это сигнал. Либо система спроектирована как слабо связанная, либо она стала таковой из-за органического роста. Разница принципиальна, но на первом этапе не важна — важно просто картографировать.

3. Найдите центры тяжести

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

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


Пример из практики

В 2003 году я присоединился к проекту по автоматизации таможенных деклараций. Кодовая база — на Delphi, 260 тысяч строк. Документации нет. Авторы — в отпуске или уже в другой компании.

Понадобилось понять, как работает механизм пересчёта валют при изменении курса. Из документации — один PDF-скриншот интерфейса.

Мы пошли по следующему пути:

  1. Обнаружили, что пересчёт вызывается при записи в таблицу
    operations_log
    .
  2. Отследили, какие формы вызывают
    INSERT
    в эту таблицу.
  3. Оказалось, что расчёт происходит через компонент TQuery, который вшит в форму визуально, без явного кода.
  4. Внутри TQuery — триггер на уровне базы данных, вызывающий хранимую процедуру с расчётом.

Таким образом, архитектура была: UI → ORM-компонент → SQL → триггер → процедура. Без единой строчки архитектурного описания.

Это знание позволило нам не переписывать расчёт, а изолировать курс в отдельную таблицу и стабилизировать расчёт через кэш.


В следующих разделах этой главы мы рассмотрим методы документирования выявленной архитектуры, способы введения soft-границ и стратегии подготовки к рефакторингу, не вызывая каскадных сбоев.

Если вы хотите — я могу сразу перейти к следующему подразделу или вставить визуальную схему того самого «распознанного» слоя архитектуры.

Продолжаю. Ниже — следующий логический подраздел главы: о фиксации и описании обнаруженной архитектуры и введении рабочих границ без полной перестройки.


Как зафиксировать обнаруженную архитектуру без иллюзий

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

1. Используйте слоистую модель

Простейший подход — разделить систему на слои:

  • Внешний — интерфейсы, API, очереди.
  • Логический — обработчики, бизнес-логика, агрегаторы.
  • Хранилище — базы данных, кэши, файловые системы.
  • Инфраструктурный — CI/CD, балансировщики, мониторинг.

Для каждой точки входа проведите путь до хранилища или выхода. Даже если получится схема типа “спагетти” — это уже результат. Вы впервые видите систему.

2. Документируйте зависимости — но не все

Главное — не пытаться документировать всё. Достаточно выделить:

  • что с чем связано напрямую;
  • что использует глобальные переменные (да, до сих пор бывает);
  • какие компоненты не могут быть остановлены без каскадных эффектов.

Это позволит выявить скрытые монолиты — то, что по коду выглядит модульно, но на деле неразделимо.

3. Не стройте UML, стройте легенду

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

4. Введите понятие «границ допустимого изменения»

На этом этапе важно зафиксировать: какие участки системы можно трогать, а какие — пока нельзя. Не потому что «не надо», а потому что нет достаточно контекста. Это — временные границы архитектурного вмешательства.

Пример: вы поняли, что вся логика расчёта скидок захардкожена в одном компоненте. Вы хотите вынести её в отдельный сервис — но пока нет тестов, и нет понимания всех точек входа. Значит, вы фиксируете компонент как «кандидат на выделение», но вводите правило: «изменения — только по внешним интерфейсам».


Почему не стоит трогать всё сразу

Многие молодые специалисты совершают ошибку: увидев архитектурный "бардак", они хотят всё рефакторить. Сразу. И правильно. Но рефакторинг архитектуры — это хирургия на работающей системе.

Любое изменение должно быть обосновано не только логикой, но и:

  • бизнес-целями (зачем?);
  • приоритетами (почему сейчас?);
  • ресурсами (кто будет это поддерживать потом?).

Если эти параметры не согласованы — лучше ограничиться наблюдением и фиксацией, чем “лечением вслепую”.


Промежуточные выводы

  1. Архитектура, которую вы не задокументировали — всё равно существует. Ваша задача — сделать её обозримой.
  2. Документация не обязана быть формальной. Она обязана быть полезной.
  3. Начинать рефакторинг без понимания центров тяжести — путь к поломке.
  4. Введение мягких границ — способ развивать систему без угрозы стабильности.

Стратегии «вживления» архитектуры в живую систему

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

1. Антикоррупционные слои (ACL)

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

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

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

Пример:
Вы хотите вынести модуль расчёта налога из старого ERP-монолита в отдельный сервис. Вместо прямой замены — создайте модуль-обёртку, который будет:

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

Таким образом, вы уже сегодня пишете архитектуру завтрашнего дня — но без риска.

2. Инкапсуляция через адаптеры

Часто рефакторинг начинается с создания вспомогательных интерфейсов, которые ещё не делают ничего нового, но уже:

  • стандартизируют вызовы;
  • сокращают знание о внутреннем устройстве компонентов;
  • создают точки будущей замены.

Это особенно важно, если внутри системы имеются прямые вызовы SQL, RPC или жёстко закодированные конфигурации. Вынесите это в адаптер — даже если он будет вызывать те же функции. Вы выиграете в будущем.

3. Структурные маркеры

Ещё одна техника — введение маркеров архитектурных намерений. Это могут быть:

  • пустые интерфейсы
    IBusinessService
    ,
    ILegacyModule
    ,
    IExternalAdapter
    ;
  • разделение модулей по папкам:
    core
    ,
    legacy
    ,
    integration
    ;
  • явно оформленные точки входа:
    entrypoints
    ,
    handlers
    ,
    jobs
    .

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

4. Встроенное логирование архитектурных границ

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

Такой подход часто позволяет опровергнуть гипотезу о необходимости рефакторинга: вы вдруг видите, что компонент, вызываемый “всеми”, на деле используется раз в неделю — и не требует срочного вмешательства.


Когда новая архитектура начинает «жить»

Система начинает меняться не с переписывания, а с того момента, когда:

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

Так приходит архитектурный контроль — не формальный, а практический. Вы ещё не переписали систему, но уже управляете её формой.


В следующей главе мы рассмотрим паттерны перехода от стихийной архитектуры к слоистой: с примерами из микросервисов, модульных монолитов и распределённых API, где история изменений сама диктует архитектурную эволюцию.

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

Прекрасно. Начну следующую главу, как и обещано — о переходе от стихийной архитектуры к контролируемой слоистой, с акцентом на реалистичные, выстраданные практики.


Глава 2

Как превратить хаос в слои, не ломая систему

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

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


Признаки архитектурного хаоса

Прежде чем перестраивать, нужно диагностировать.

Вот основные симптомы:

  • Компоненты вызывают друг друга напрямую, без формализованных интерфейсов.
  • Повторяется логика авторизации, валидации или обработки ошибок — в разных местах.
  • Нет возможности протестировать бизнес-логику отдельно от инфраструктуры.
  • Любое изменение требует затрагивать сразу несколько участков системы.
  • Нарушено логическое разделение: в одном модуле соседствуют SQL-запросы, JSON-сериализация и расчёт скидок.

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


Первый шаг: ввести уровень представления

Начните с внешнего слоя — интерфейсов взаимодействия: REST-эндпоинтов, CLI-команд, cron-задач, подписчиков очередей.

Задача: отделить интерфейс от логики.

Пример:

Теперь всё, что связано с бизнес-логикой, вынесено в

order_service
, который можно тестировать, заменять, расширять.


Второй шаг: вынести бизнес-логику в сервисный слой

Сервисный слой — это посредник между контроллерами (или слушателями) и инфраструктурой (БД, API, очереди).

Хороший сервис:

  • ничего не знает о HTTP или AMQP;
  • не зависит от конкретной ORM;
  • работает только с DTO и моделями;
  • имеет стабильные публичные методы.

Этот слой становится ядром приложения. Именно вокруг него вы в дальнейшем сможете выстраивать адаптеры, тесты и новые модули.


Третий шаг: изолировать инфраструктуру

Инфраструктурные зависимости — база данных, кэш, внешние API — не должны быть жёстко привязаны к логике.

Используйте интерфейсы и внедрение зависимостей (dependency injection), даже если вручную.

Пример:

Теперь смена платёжной системы — не архитектурная катастрофа, а замена одной реализации интерфейса.


Четвёртый шаг: локализовать побочные эффекты

Слои архитектуры — это не только логическое разделение, но и изоляция побочных эффектов: записи в базу, внешние вызовы, логирование.

Золотое правило: бизнес-логика не должна знать, как логируются ошибки. Она должна просто выбрасывать исключение или возвращать статус. Всё остальное — дело адаптеров и middleware.


Визуализация перехода

Переход от неструктурированной архитектуры к слоистой обычно выглядит так:

[Стихийно] UI → Валидация → SQL → Бизнес-логика → Ответ [После структурирования] Контроллер ↓ DTO → Сервис → Репозиторий ↓ Внешние адаптеры (DB, API)

Появляется слой, который можно стабилизировать, покрыть тестами, описать, вынести в отдельный пакет или сервис.


В заключение

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

Архитектура как результат, а не предпосылка

В большинстве проектов архитектура появляется не как проектное решение, а как побочный эффект разработки. Сначала нужно было “просто сделать MVP”, потом “срочно прикрутить оплату”, затем “внедрить новый API для партнёров”, и вот уже десятки компонентов срослись в один организм без плана, но с историей. И всё это нельзя выбросить — оно работает, приносит деньги, и в его хаосе живёт логика.

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

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

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

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

Архитектура должна позволять действовать на ощупь

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

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

Архитектура требует дисциплины

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

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

Время — главный союзник архитектуры

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

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

Архитектура — это не слои, а форма мышления

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

Настоящая архитектура проявляется в том, как вы отвечаете на вопросы:

  • Могу ли я заменить компонент без эффекта для остальных?
  • Понимаю ли я, кто использует мой интерфейс?
  • Знаю ли я, откуда приходит вход, и куда уходит результат?
  • Могу ли я описать этот модуль без чтения кода?

Если хотя бы на часть этих вопросов вы отвечаете «да» — у вас уже есть архитектура. Остальное — дело структуры.

Архитектура всегда создаётся изнутри

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

Архитектура не навязывается. Она вырастает. И если система растёт правильно, со временем вы обнаружите, что больше не говорите “мы делаем рефакторинг”. Вы просто продолжаете развивать систему. И всё встаёт на свои места.


Глава 3

Стабильность через ограничения: зачем архитектуре жёсткость

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

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

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

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

Самая разрушительная архитектура — это та, которая кажется гибкой, но не определяет рамок. Где всё зависит от контекста. Где один модуль знает всё про другой. Где структура формируется каждый раз заново. Такая система не масштабируется. Она требует эксперта. Того самого, кто «всё знает». Если он уходит — система становится неуправляемой.

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

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

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

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

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

Глава 4

Архитектура и команда: совместное пространство решений

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

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

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

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

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

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

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

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

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

Главная цель архитектуры — быть воспроизводимой. Не за счёт принуждения, а за счёт очевидности. Если модуль не требует объяснения — он вписан. Если структура повторяется — она принята. Если интерфейс понятен без описания — он живой. Архитектура, которая требует инструкций, уже проиграла.

Поэтому архитектор работает не с кодом. Он работает с командой. Он наблюдает, где возникают отклонения. Где появляются нестандартные пути. Где структура нарушается — не из злого умысла, а из непонимания. И устраняет не последствия, а причины. Через объяснение. Через адаптацию. Через пересмотр. Архитектура — это не контроль, это среда.

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

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

Примеры, которые не забудешь

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

Налоговая платформа: три ORM внутри одной транзакции

В 2011 году я сопровождал крупную систему расчёта налогов на импорт. Архитектуру писали «по ходу», менялось правительство, менялись курсы валют, а сроки всегда были «на вчера». Внутри одного запроса система обращалась к трём разным ORM: один разработчик использовал Hibernate, второй — iBatis, третий — прямые JDBC-запросы, обёрнутые в DAO.

Три ORM в одном процессе. Каждая работала со своей сессией. В случае ошибки откат происходил в двух местах из трёх. Третье — жило своей жизнью. Иногда данные сохранялись частично, иногда — дублировались, иногда — не сохранялись вовсе. Бизнес не понимал, почему расчёты ведут себя непоследовательно, хотя интерфейс всегда отвечал "200 OK".

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

Грузовая логистика: маршрутизация через Excel

В 2015 году мы получили на поддержку систему маршрутизации грузов по территории СНГ. Архитектура на первый взгляд была нормальной: API, сервисы, база данных. Но расчёт маршрутов — ключевая функция системы — происходил через вызов Excel-файла с макросами. Веб-сервер в фоне запускал Excel.exe через COM, открывал файл, заполнял ячейки, запускал макрос и забирал результат.

Файл с логикой маршрутизации был "находкой" одного из аналитиков. Его никто не трогал, потому что он "работал". Через три года макрос начал вести себя нестабильно, Excel стал зависать на продакшене. Обновление Office сломало совместимость, а человек, написавший макросы, давно уехал в Канаду.

Расчёт логистики остановился. По всей стране.

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

Государственная система записи в школы: все запросы — в админку

2020 год. Пиковая нагрузка — открытие записи в образовательные учреждения. Система упала через 6 минут после старта. Причина — архитектурная особенность: все внешние HTTP-запросы, в том числе от родителей и школ, обрабатывались внутри административного интерфейса, защищённого сессией и CSRF-токенами.

Когда нагрузка достигла 10 000 RPS, каждый запрос требовал запуска логики рендеринга админки, проверки прав доступа и генерации HTML. Ответ — JSON, но путь к нему — через Django Admin. Код был унаследован с MVP-периода. Он «просто работал».

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

Финтех на микросервисах: 62 сервиса и ни одного контракта

Один проект финтех-платформы в 2022 году гордился своей "современной архитектурой": микросервисы, Kafka, CI/CD, Kubernetes. Всего — 62 сервиса. На деле — 62 независимых приложения, каждое со своей моделью пользователя, своими сообщениями, своими протоколами.

Ни один интерфейс не был задокументирован. Ни один event не был стандартизирован. Любая попытка заменить логику в одном сервисе приводила к обрушению четырёх других. Коммуникация между сервисами происходила по произвольно сформированным JSON-структурам, без схем, без валидации, без обратной совместимости.

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

Платёжный шлюз: бизнес-логика в кэше

Последний пример. 2018 год. Платёжная система обрабатывает запросы банков, делает скоринг, проверяет лимиты. Внутри Redis — хранятся все лимиты, состояние транзакций, статус антифрода. Ни одна из этих сущностей не существует в БД. Кэш стал первичным хранилищем. Он жил — всё работало. Redis перезагрузился — система потеряла состояние. Все лимиты обнулились. Банки начали проводить повторные транзакции. Финансовый ущерб — миллионы.

Кэш не архитектурный слой. Это механизм оптимизации. Когда система превращает кэш в источник истины, она подменяет структуру временным решением. И это не ошибка — это архитектурная катастрофа.

Глава 5

Архитектура как следствие ответственности

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

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

В зрелой системе каждый компонент имеет свою зону контроля. Это не обязательно один человек. Это может быть команда. Это может быть автоматическая проверка. Но где-то должно быть принято решение: "этот кусок — наш, и он работает вот так". Ответственность фиксирует смысл. Без неё структура распадается.

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

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

Если бизнес-логика начинает зависеть от структуры рендера — значит, ответственность за данные и представление не разведены. Если адаптер внешнего API изменяет внутренние объекты напрямую — нарушено разграничение зон воздействия. Система становится связанной вглубь. Любое изменение превращается в каскадное.

Нормальной практикой становится установка правил, которые блокируют переход ответственности между слоями. Например:

  • бизнес-логика не имеет доступа к HTTP-запросу;
  • репозиторий не знает о формате данных клиента;
  • адаптер не имеет права напрямую обращаться к сервисам.

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

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

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

Потому архитектура начинается не с файловой структуры. Она начинается с вопроса: кто за что отвечает? Кто гарантирует, что компонент работает? Кто определяет его поведение? Кто принимает изменения? И пока на эти вопросы нет ответов — никакая структура не спасёт проект от распада.

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

Глава 6

Архитектура как средство управления изменениями

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

Изменения неравномерны. Часть компонентов не трогается годами, другая — переписывается каждый спринт. Архитектура должна это учитывать. Не выравнивать, не нормализовать — а позволять системе изменяться локально, не затрагивая остального.

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

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

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

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

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

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

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

Для каждого изменения в зрелой архитектуре должны существовать:

  • место входа — где начинается поведение;
  • слой логики — где оно обрабатывается;
  • точка хранения — где фиксируется результат;
  • точка выхода — где возвращается ответ.

Если хотя бы один из этих элементов отсутствует или размывается — изменение становится опасным. Оно выходит за пределы своей зоны. Архитектура должна это предотвращать.

Ещё один аспект — совместное существование старого и нового. Архитектура должна позволять новой логике внедряться рядом со старой. Без замены. Без разрушения. Это достигается через версионирование, адаптеры, feature-флаги, параллельные интерфейсы. Главное — не замыкать систему на единственный путь. Будущее всегда будет другим. И архитектура должна быть готова к этому.

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

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

Реальный случай: переписанный API, который отключил всю страну от соцвыплат

2021 год. Государственная информационная система, отвечающая за обработку и верификацию заявлений на социальные выплаты. Централизованный backend — монолит, с хорошо организованной базой данных, но устаревшим REST API. Требование: внедрить новую схему подтверждения заявлений по регионам — с расширенной логикой и параметрами фильтрации.

Молодая команда берётся «оптимизировать» API. Вместо версии 1.2 они выпускают 2.0 — без обратной совместимости. Они переписывают endpoint

/applications
так, чтобы он принимал новые параметры, возвращал новые структуры, обрабатывал их по-другому. Старый endpoint удаляют через
git rm
, без feature-флагов, без fallback, без версионирования.

Результат: фронтенды в 79 регионах, всё ещё использующие старую реализацию, начинают получать 500 Internal Server Error. Заявления не уходят. Очереди в МФЦ растут. Люди не могут подать заявку на детские выплаты, пособие по инвалидности, компенсации ЖКХ. Некоторые заявки теряются. Несколько регионов переводят обработку на бумажные формы. Возникает политический кризис — на федеральный уровень поступают обращения.

Выясняется, что:

  • Старый API использовался мобильным приложением, которое не обновлялось с марта — и его невозможно принудительно обновить.
  • Внешние системы интеграции (например, с Пенсионным фондом) обращались к тому же endpoint, без документации.
  • Никакой документации по старому API не осталось — человек, писавший её, ушёл год назад.
  • CI/CD выкатывал изменения напрямую в прод, без canary-инфраструктуры.

Решение принимали два человека. Один разработчик — инициатор, второй — тимлид, который “не видел смысла в старом коде”. Архитектор проекта не участвовал — он был в отпуске.

Система легла на три дня. Откат невозможен — база уже изменилась. Экстренно формируется "антикризисная сборка", которая имитирует старый API через фасад поверх нового. Срок восстановления полной работоспособности — 11 дней.

Это не проблема кода. Не проблема DevOps. Это архитектурная катастрофа, вызванная отсутствием структуры управления изменениями.
Нет версионирования.
Нет обратной совместимости.
Нет контракта.
Нет границ.

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

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

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

Глава 7

Архитектура и время: работа с устареванием

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

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

Первая задача — признание. Нужно признать, что любой компонент, написанный сегодня, со временем станет неподдерживаемым. Человек, его написавший, уйдёт. Зависимости устареют. API изменится. Бизнес потеряет интерес к логике, которую он реализует. Устаревание — это не гипотеза, а факт.

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

Третья задача — сопровождение. Устаревшие компоненты нужно отслеживать. Нельзя позволять им становиться невидимыми. Каждому из них должен соответствовать мета-статус: «в процессе замены», «в изоляции», «устойчивый, но не развиваемый». Эти статусы не являются административными. Это архитектурные признаки, отражающие динамику системы.

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

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

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

Ещё один принцип — техническая эстафета. Когда компонент устаревает, он должен быть передан. Не просто сохранён в git. Не просто оставлен “как есть”. Он должен быть сопровождаем. Через документацию. Через автоматизацию. Через адаптер. Через реплику. Архитектура должна предусматривать передачу компонентов между поколениями разработчиков. Тогда система становится преемственной.

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

Самый опасный симптом архитектурного устаревания — это рост усилий на простые действия. Добавление поля требует двух недель. Новый endpoint ломает три старых. Тесты работают только на старой версии зависимостей. Всё ещё работает — но каждое изменение даётся через сопротивление. Это значит: архитектура перестала двигаться во времени. Она застыла. А значит — начала умирать.

Поэтому архитектура должна быть не только модульной, но и временной. Она должна учитывать, что код живёт в эпохах. Что всё, что создаётся, будет заменено. Что всё, что работает, будет устаревать. И система, способная это осознавать — будет жить долго. Потому что она умеет меняться. Не разрушаясь, не отрываясь от прошлого. А проходя через него.

Пример: критический баг в коде, который никто не трогал с 2009 года

В 2020 году мне довелось участвовать в стабилизации информационной системы одного из ключевых государственных регистров. Система пережила многое: смену языков программирования, смену подрядчиков, миграции с Access на PostgreSQL, внедрение слоёв поверх слоёв, десятки слепых «заплаток». Её никто не переписывал полностью — каждый новый подрядчик «перестраивал по частям». Но одна часть кода оставалась нетронутой. С 2009 года.

Это был модуль расчёта цифровых подписей для XML-документов. Написанный на чистом C, внедрённый через обёртку в COM-интерфейс, вызываемый из .NET через late-binding. Никто из работающих на проекте уже не понимал, как он работает. Знали только одно: если его убрать — подписи не проходят валидацию в системах органов власти.

Файлы этого модуля не были в основном репозитории. Хранились отдельно, в архиве, с пометкой «не трогать». Их просто копировали вручную при деплойменте.

Однажды команда DevOps решила «очистить» pipeline. Стандартизировать всё, избавиться от лишнего. Подключили Code Signing, обновили конфигурации, удалили «неиспользуемые» артефакты. Среди них — и архив с C-модулем.

На следующее утро все подписанные документы из системы стали юридически недействительными. Удаление библиотеки нарушило внутреннюю структуру подписи. Подписи стали проходить XML-валидацию, но не проходили криптографическую проверку в удостоверяющих центрах. Документы принимались в реестре — и отбрасывались на этапе правового подтверждения. Это произошло в день подачи отчётности по десяткам тысяч объектов.

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

Этот модуль стал технической капсулой времени. Он не был ни задокументирован, ни версионирован, ни протестирован. Но от него зависела работа всей системы. Никто его не трогал — и именно поэтому он прожил одиннадцать лет. А потом — стал триггером кризиса.

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

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

Глава 8

Архитектура как договор: взаимодействие между частями системы

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

Без этого соглашения модули начинают «разговаривать» друг с другом напрямую. Передают объекты, которые не предназначены для внешнего использования. Делятся внутренним состоянием. Нарушают инкапсуляцию. Возникают связи, которые не фиксируются в коде явно, но проявляются при каждом изменении. Система становится хрупкой. Любое движение одного компонента — ломает поведение другого.

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

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

Контракт должен быть:

  • Явным — видно, где начинается и заканчивается взаимодействие;
  • Стабильным — изменение требует осознанного обновления;
  • Однозначным — невозможны двусмысленные трактовки;
  • Самодостаточным — всё, что нужно для использования, содержится в самом контракте.

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

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

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

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

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

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

Чем дальше система живёт, тем важнее становится этот договор. Потому что всё остальное — будет меняться. Но именно структура взаимодействия останется. И от того, насколько она была правильно оформлена в самом начале, будет зависеть, сможет ли система идти дальше.

Пример: "простой" рефакторинг, который отключил платёжную систему на 48 часов

2022 год. Внедрение новой версии ядра внутренней CRM в крупной логистической компании. Система обрабатывает все финансовые операции: создание заказов, расчёт доставки, автоматическое выставление счетов и привязку оплаты через внешний шлюз. Компоненты формально разделены, но… взаимодействуют «по-человечески».

Вся логика генерации счёта реализована в модуле

BillingService
, который возвращает объект
InternalBillModel
. Его структура зафиксирована нигде — в комментариях и в головах. Этот объект, напрямую, без адаптации, прокидывается:

  • во внешний платёжный шлюз, как payload;
  • в PDF-генератор, как шаблон;
  • в email-сервис, как вложение;
  • в REST API, как тело ответа клиенту.

Ни один из этих компонентов не фиксирует схему данных. Всё — «по договорённости».

Один из разработчиков обнаруживает, что поле

amount_total
в
InternalBillModel
содержит строку (
"1200.00 RUB"
), а не числовое значение. Он исправляет: меняет тип на
Decimal
, удаляет суффикс валюты — "чтобы всё было правильно".

Никаких тестов на обратную совместимость. Никакого внешнего контракта. Никакой схемы. Коммит проходит ревью: «мелкая правка, тип поправили». Код попадает в прод.

Через 3 часа начинаются отказы: шлюз платежей возвращает ошибку «Invalid amount format», PDF-файлы не собираются — формат не соответствует шаблону, почтовый сервис падает при попытке сериализации, фронтенд начинает отображать

NaN
в строке оплаты.

Финансовые транзакции по всей стране останавливаются.

48 часов: поиск причины, попытки отката, ручной парсинг логов, восстановление утерянных объектов. Платёжная система простаивает. 8 миллионов рублей заморожено в ожидании подтверждений. Ключевые клиенты блокируют договора до устранения.

Это не баг. Это нарушение контракта, которого никогда не было. Разработчик изменил реализацию, не осознавая, что она уже является интерфейсом. Потому что взаимодействие между модулями — это всегда контракт. Независимо от того, описан он или нет.

Если бы схема

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

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

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

Глава 9

Архитектура как язык: как проект начинает говорить сам за себя

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

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

Плохо оформленная система говорит на диалекте. Каждый модуль сделан по-своему. Где-то бизнес-логика в контроллере, где-то — в middleware, где-то — в модели. Названия ничего не значат. Поведение непредсказуемо. Структура не повторяется. И тогда код требует постоянного устного перевода: через менторов, ревью, комментарии, устные договорённости.

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

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

OrderService
, в другом —
OrderProcessor
, в третьем —
OrderLogic
. Смысл теряется. Архитектурная речь распадается на шум. Но если всё оформлено единообразно — даже без документации видно, как система устроена.

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

Язык проявляется в мелочах. В том, как называются методы. В том, как делятся файлы. В том, как вы оформляете исключения. В том, как выстраиваются зависимости. Архитектурная речь — это не формат, а интонация. И если проект сделан правильно, вы чувствуете: здесь есть стиль. Здесь есть структура. Здесь всё говорит.

И это касается не только кода. В инфраструктуре, в CI/CD, в тестах, в конфигурации — везде должна быть одна и та же логика. Одинаковое оформление пайплайнов. Повторяющиеся правила сборки. Стандартизированное логирование. Архитектура проявляется в последовательности. И если её нет — система молчит. И каждый новый разработчик вынужден слушать шум, пытаясь различить в нём смысл.

Архитектура как язык — это способ проектировать систему, которая разговаривает с человеком, а не требует расшифровки. Такая система не путает. Не требует устного наследования. Не вызывает страха. Она отвечает на вопросы ещё до того, как они заданы. Именно поэтому зрелая архитектура — это не просто форма. Это способ выражения.

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

Пример: «Где логика?» — или хроники архитектурной немоты

Компания средней руки, веб-платформа для логистических операций. Вроде бы всё по канонам: REST API, микросервисы, слой бизнес-логики, тесты, CI. Команда — 18 человек, сменяемость высокая, но процессы выстроены. Или так казалось.

Молодой разработчик Дима берёт задачу: «Добавить возможность приостанавливать заказ на любом этапе». Простая фича. Добавить кнопку, прокинуть состояние, отреагировать на него в логике. Он открывает код и... теряется.

День 1

Дима (в чате):
Ребята, а у нас

OrderService
отвечает за обновление статуса, верно?

Ответ №1 (от Тимура):
Не-а, это в

OrderProcessor
. Там весь переход состояний.

Ответ №2 (от Марка):
Ну вообще да, но только если это не «заморозка» — её добавляли полгода назад в

OrderManager
.

Ответ №3 (от Ксюши):
В

OrderHandler
, кажется. Или
OrderFlowController
. Я через него делала отмену.

Дима:
То есть «обновление статуса» — это три разных класса?

Тимур:
Нет, их пять.

OrderStateUpdater
— ещё в
utils
, он делает set напрямую.

Марк:
Только не трогай

OrderStateLogic
, там всё завязано на старую модель. Упадёт миграция.

День 2

Дима решает разобраться. Находит

OrderProcessor
, в нём — вызов
updateStatus(order, newStatus)
, который на деле вызывает
OrderWorkflow.update(order.id, newStatus)
. Этот, в свою очередь, обращается к
StateEngine
, где маппинг между статусами сделан через
HashMap<Enum, Function<Order, Boolean>>
. Ни одной валидации. Никакого контроля переходов. Просто функции, написанные «на глаз».

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

OrderHooks
.

Дима (на стендапе):
Ребят, я нашёл шесть компонентов, которые меняют статус заказа. У них у всех разные правила. Один просто меняет поле, другой вызывает внешний API. Это норм?

Тимур:
Ну, они писались в разное время. Под разные кейсы.

Ксюша:
Мы как-то обсуждали, что надо это объединить, но времени не было.

Марк:
Главное — не трогай

OrderStateLogic
. Там всё через рефлексию. Кто тронет — сам чинит.

Дима:
То есть у нас нет ни одного места, где можно понять, что вообще делает система при смене статуса?

Тимур:
Да всё просто: ищи по

status =
, да и всё.

Дима:
Но их сотни.

Ксюша:
Ну да. У нас же гибкий подход. Мы не бюрократы, хе-хе.

День 5

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

Падает баг. Ему говорят: «не предусмотрел все случаи». Но где они описаны — не знает никто. Архитектуры взаимодействия — нет. Только спонтанная речь кода. Слова одни и те же (

pause
,
hold
,
freeze
,
stop
), но каждый говорит на своём диалекте.

И каждый следующий разработчик вынужден изучать этот язык с нуля.


Итог: система не имела архитектурного языка.

  • Компоненты назывались одинаково, но значили разное.
  • Поведение дублировалось без центра.
  • Структура не передавала смысла.
  • Код был многословным — но молчаливым.

Все модули «что-то делали». Но проект ничего не говорил о себе. Разработчик не мог прочитать его. Не мог понять. Не мог продолжить.

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

Глава 10

Архитектура и границы понимания: как не знать всё — и всё же управлять

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

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

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

Для этого архитектура должна быть устроена так, чтобы:

  • каждая часть была ограничена по области ответственности;
  • взаимодействие между частями шло через явно заданные интерфейсы;
  • зависимости были направленными и контролируемыми;
  • поведение было локализуемым и предсказуемым.

Глобальная архитектура проявляется не в том, что все знают всё. А в том, что все не знают, но могут действовать. Структура должна удерживать систему от хаоса там, где заканчивается понимание.

Это достигается через несколько принципов.

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

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

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

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

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

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

Архитектура — это не то, что ты знаешь. Это то, что тебе не нужно знать, чтобы не сломать систему.

Пример: «Я думал, это просто метод...» — или как система рушится в незнании

В один не самый примечательный вторник в команду разработки крупного онлайн-сервиса пришёл новый разработчик — Артём. Специалист толковый, прошёл собеседование с задачами на рекурсию, SOLID и инъекции зависимостей. Первый рабочий день, знакомство с проектом. Приложение большое, но вроде бы структурированное: backend на Spring Boot, десятки микросервисов, Swagger, инструкции в Wiki.

Артёму дают задачу: “изменить формат отображения статуса заказа на фронте — вместо ‘IN_PROGRESS’ показывать ‘В процессе выполнения’”. Простая задача, связанная только с визуализацией. Он находит метод

getOrderStatusText()
в классе
OrderUtils
, который возвращает строку. Всё очевидно:

Артём меняет его:

Тестирует на стенде — всё работает. Пушит. Через два часа — прод.

А через три — падают все экспортные отчёты, ломается интеграция с SAP, не проходят события в Kafka, мобильное приложение выдаёт ‘unknown status’. Начинается разбирательство.

Оказывается, метод

getOrderStatusText()
использовался не только для фронта. Он прокидывался:

  • в отчёты (использовался как ключ группировки заказов);
  • в Kafka-сообщения, от которых зависел billing;
  • в email-уведомления для клиентов;
  • в внешние системы, ожидающие строгое значение enum'а.

И самое главное: никакой документации о том, где он используется, не существовало. Ни одного теста на побочные эффекты. Никакой фиксации контракта. Просто «утилита», которая «возвращает текст статуса». В системе, где текст = семантика.

В результате:

  • откатываются последние 6 коммитов;
  • пересобираются очереди Kafka;
  • восстанавливаются сотни ошибочных событий по логам;
  • запускается ручной экспорт отчётов в Excel;
  • клиентам отправляются письма с извинениями за "неверный статус".

Артёму никто не сказал, что он сделал ошибку. Потому что он не сделал. Ошибка была в архитектуре, которая позволяла:

  • использовать один метод для четырёх разных смыслов;
  • не фиксировать, где и зачем он используется;
  • не разделять интерфейс для клиента и интерфейс для системы.

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

Этот случай запустил пересмотр всей стратегии:

  • ввели отдельный слой DTO для фронта;
  • запретили использовать
    toString()
    в публичных API;
  • установили правило: всё, что уходит наружу, должно быть типизировано и версионировано;
  • начали писать тесты не на логику, а на поведение в контракте.

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

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

Глава 11

Архитектура и эволюция: как система становится собой

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

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

Каждое изменение — это вызов архитектуре. Когда появляются новые требования, когда модуль начинает использоваться иначе, когда приходится внедрять очередную “заплатку” — это не ошибки. Это естественные точки роста. Именно в этих точках архитектура должна проявить себя не как сопротивление, а как рамка развития.

Есть архитектуры, которые рассыпаются при малейшем отклонении. Каждое новое поведение противоречит прежним структурам. Каждое расширение требует переписывания. Такие системы обречены. Потому что они строились как закрытые формы.

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

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

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

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

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

Ошибки на этом пути неизбежны. Любая развивающаяся система проходит через фазы:

  1. Стихийного роста — когда всё строится вокруг фич, без общей логики;
  2. Рационализации — когда выделяются слои, границы, общие механизмы;
  3. Избыточной формализации — когда система становится перегруженной правилами;
  4. Устойчивого равновесия — когда структура служит развитию, не мешая ему.

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

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

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

Пример: «Чья корова — тот и сервис»

или как архитектура спасла от полной абсурда

2023 год, сервис аренды электросамокатов. У компании — бурный рост, фичи льются одна за другой: пополнение баланса, зона возврата, динамические тарифы, страховка от дурака и от дождя. Команда backend’а — 14 человек, фронт — 7, DevOps — в отпуске. Система на микросервисах, но в реальности — микропаниках.

Внутри архитектуры — сущность

Ride
. У неё есть статус, координаты, пользователь, тариф, и (внимание) — привязанное животное. Да, в какой-то момент добавили фичу "зоопрокат": можно было арендовать самокат, держащийся рядом с козлёнком или с ламой — как сопровождение по паркам.

Эта фича умерла через месяц, но поле

ride.animal_id
осталось. А вместе с ним — и весь связанный код. Потому что, как позже выяснилось, часть расчёта тарифа продолжала учитывать наличие животного — "на всякий случай".

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

Джуниор-специалист Паша, не разобравшись, где происходит расчёт, вносит правку:

Проблема в том, что

animal_id
больше нигде не проверяется. А самокат, где в 2022 году тестировали зоопрокат, до сих пор числился с
animal_id = "horse"
. И он ездил по центру Москвы.

И вот клиенты, арендующие обычный самокат, начинают получать на баланс по 7 рублей бонуса за каждую поездку.
По 20 поездок в день.
15 тысяч пользователей.
По 140 рублей в сутки — только потому, что в системе осталась метка: "лошадь".

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

“Нам бы знать, где эта чёртова лошадь…”

И тут архитектор — пожилой, молчаливый, с бумажной тетрадкой, открывает карту и говорит:

“Вот она. Самокат с ID 4409. У него в Redis ещё остался ключ

animal:horse
. Очистим — и закроем контракт.”

Щелчок пальцев. Один

del
в Redis. Начисления прекращаются. Бонус — обнуляется. Паника — уходит. Почему сработало?

Потому что архитектура предусматривала внешний слой обёртки над старой фичей

AnimalCompanionAdapter
. Он был внедрён год назад, когда зоопрокат сворачивали. Неудалён, а просто вынесен в адаптер с единственным конфигом:
"enabled": false
.

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

Мораль?

Архитектура, в которой даже забытые фичи изолированы, позволяет действовать.
Даже если речь идёт о лошади, записанной в Redis.
Даже если никто уже не помнит, зачем она там.
Архитектура не судит. Она просто даёт выключатель.

Глава 12

Архитектура и ошибки: как проект должен падать

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

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

Хорошая архитектура, напротив, позволяет ошибкам проявляться в нужных местах. Она определяет, где ошибка — это норма, а где — сигнал. Где её можно пережить, а где — нужно прервать выполнение. Где логировать, а где паниковать. И всё это — не в коде, а в структуре.

Первая и главная задача архитектуры — разделить критическое и некритическое поведение. Например, отказ внешнего API в системе логирования — не причина падения основного сервиса. А вот нарушение бизнес-инварианта — должно останавливать выполнение, даже если система “технически” может продолжить.

Это разделение должно быть видно в структуре проекта:

  • Внешние вызовы — всегда обёрнуты;
  • Обработка ошибок — централизованная;
  • Логика падения — конфигурируема;
  • Контракты — с чётко определёнными реакциями.

Вторая задача архитектуры — обеспечить прозрачность ошибок. Каждый сбой должен иметь путь наверх. Не замалчиваться, не отбрасываться, а доходить до той точки, где о нём могут принять решение. Это значит: ошибки не должны тонуть в слоях. Ошибка в инфраструктуре не должна маскироваться бизнес-исключением. Ошибка бизнес-логики — не должна уходить в 200 OK с пустым телом.

Система должна говорить: «здесь что-то пошло не так». И говорить это внятно.

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

Именно поэтому важно проектировать так, чтобы каждый модуль мог выйти из строя локально. Отдельно. Не разрушая остальное. Это достигается через:

  • чёткие интерфейсы, которые возвращают статус;
  • слой адаптации, который отделяет бизнес-логику от внешнего мира;
  • централизованную обработку ошибок;
  • шаблоны поведения на случай сбоя (fallback, retry, circuit breaker и др.).

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

Именно в момент сбоя выясняется:

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

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

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

Пример: «ОСАГО, которого не было»

или как одна архитектурная ошибка отменила всю автоматизацию

2020 год. Один из региональных центров обработки данных получил федеральное задание: построить сервис автоматической проверки наличия полиса ОСАГО в реальном времени, привязанный к уличным камерам фиксации нарушений.
Идея была проста: камера считывает номер → сервис проверяет по базе РСА → если ОСАГО нет — формируется административное дело.
Масштаб — весь регион. Сотни тысяч транспортных средств.

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

  • микросервис “CameraListener” принимает номера с камер;
  • микросервис “PolicyChecker” обращается к внешнему API РСА;
  • микросервис “CaseInitiator” формирует дело и пишет в СУБД.

Всё разнесено. Всё по науке. Диаграммы красивые.

Но он не предусмотрел одного: что API РСА может упасть.

Нет: он знал, что оно может упасть.
Но, как он выразился, “в случае ошибки запрос можно повторить через пять секунд”.

Что он сделал:

  • прямой вызов внешнего API из
    PolicyChecker
    , синхронно;
  • отсутствие кэширования результатов;
  • никакой обработки таймаутов или отказов на уровне архитектуры;
  • отсутствие очередей или retry-механизмов;
  • и, главное — если API РСА не отвечает, вся цепочка рушится: не формируется дело, не сохраняется факт нарушения, ничего не логируется.

И вот первый пилот:
В центр поступают номера. Нагрузка — 6000 запросов в минуту. РСА не выдерживает. Их API начинает возвращать 502, 504, Connection reset.
Система встаёт. Не потому что упал весь сервис. А потому что архитектура не позволила ему жить при сбое одной части.

Разработчик:
“А мы хотя бы сохранили номер и время, чтобы потом повторить?”

Ответ архитектора:
“А зачем? Это же реальное время. Мы же их не задерживаем.”

Нарушения теряются.
Данные не откладываются.
Очередей нет.
Логов — нет.

В течение трёх дней пилота из 180 тысяч срабатываний камер только 8% прошли полный цикл. Остальные — провалились в никуда. Инициаторы проекта получают выговор. Финансирование остановлено.
Сервис признают “неэффективным” и “недостаточно зрелым для эксплуатации”.

А всё, что требовалось:

  • кэшировать результаты запросов на время TTL полиса;
  • использовать очередь между камерами и проверкой;
  • делать запросы в фоне;
  • сохранять номер и таймстемп при сбое;
  • инициировать разбирательство асинхронно, а не в режиме онлайн.

Любой из этих механизмов — типовое архитектурное решение для работы с внешними зависимостями.
Но архитектор не спроектировал падение.
Он не подумал, как система будет вести себя в случае сбоя.
А значит — система была обречена с самого начала.

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

Глава 13

Архитектура как поведение под давлением

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

Именно в такие моменты становится ясно, зачем нужна архитектура. Не чтобы всё было красиво — а чтобы система продолжала функционировать, даже если обстоятельства стали враждебными.

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

  • компоненты связаны напрямую;
  • нет тайм-аутов;
  • нет изоляции;
  • нет слоёв защиты;
  • и главное — нет проектного решения на случай, если что-то идёт не так.

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

Структура должна позволять системе:

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

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

Хорошо спроектированная архитектура допускает:

  • временное отключение части функции, без влияния на остальное;
  • кэширование при недоступности источника данных;
  • graceful degradation — “мягкое ухудшение” опыта пользователя;
  • fallback-логику, заранее спроектированную и проверенную.

Плохо спроектированная — просто падает. Или, что ещё хуже, ведёт себя непредсказуемо: часть системы продолжает работать, но нарушает инварианты, оставляет невалидные данные, или создаёт неконсистентное поведение, которое потом невозможно восстановить.

Зрелая архитектура включает в себя:

  • ограничения на уровне SLA (service-level agreements);
  • явную границу между “внутренним” и “внешним”;
  • проектирование поведения при пиковых нагрузках;
  • отслеживание отклонений — как функциональных, так и структурных.

Важнейший принцип: вы не контролируете входящие данные, вы контролируете своё поведение. Неважно, что происходит снаружи. Важно, что система:

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

Если структура это не поддерживает — у вас нет архитектуры. У вас есть совокупность модулей, пока что живущих за счёт удачи.

Под давлением проявляется суть:
Архитектура — это не как работает.
Архитектура — это как не ломается.

Пример: «Она ушла в 504»

или как архитектура разрушила отношения (но спасла сервис)

Он был backend-разработчиком.
Она — менеджером продукта.
Познакомились на ретроспективе. Посмотрели друг на друга, как система смотрит на лог и думает: “ты здесь не просто так”.

Её звали Лена. Ему — было всё равно, пока CI зелёный.
Она делала фичу “Отложенная доставка букетов”, приуроченную к 8 марта. Он — отвечал за обработку заказов.

— "Мы хотим, чтобы пользователь мог выбрать любое время доставки в будущем!" — говорила она.
— "У нас вся система построена на

now()
. У нас время — это истина!" — отвечал он.

Они спорили. Он говорил: “не получится”. Она говорила: “сделай красиво”.
Он по ночам делал

ScheduledDeliveryService
.
Она рисовала стрелочки на Miro и ставила
@here
в три часа ночи.
Кажется, это было началом чего-то настоящего.

Сервис запустили. Первый заказ: розы с доставкой 8 марта, 6:00. В системе UTC. На продакшене — московское время. В облаке — Лондон.
Клиент получает пуш 7 марта в полночь:
"Ваш букет уже в пути!"
Но курьера ещё не было.
А потом началось.

Сервер стал тормозить. Очередь доставки выросла до 200 000 задач. Таймеры стреляли не по расписанию. Сервис начал выдавать 504 Gateway Timeout, потому что

DeliveryScheduler
рассчитывал всё в одном потоке.
Розы не доезжали. Люди писали гневные отзывы.
Один клиент заявил, что его букет доставили до развода.

Она пришла к нему вечером. Грустная.
— “Ты же говорил, что продержится.”
Он промолчал. Открыл консоль. Удалил всё. И сказал:

“Сделаем через очередь.

Сначала в

scheduled_events
, потом —
worker
,

потом —

delivery trigger
.

Таймзоны фиксируем через

context
.

А время больше не будет истиной.

Время будет мнением, которое можно отложить.”

И она улыбнулась.
И всё заработало.
Нагрузку распределили. Таймеры стали обрабатываться по пачкам.
Система больше не паниковала, когда букет должен был быть доставлен в будущем.

А через неделю она прислала ему Slack-сообщение:

“Хочу встретиться.
Не как product.
А как пользователь.
Которому ты однажды доставил то, что нужно.
В нужное время.”

Мораль?

Любая архитектура должна уметь жить под давлением.
Даже если это давление — 8 марта и очередь из 150 тысяч заказов с привязкой ко времени.
Даже если ты устал. Даже если ты backend-разработчик и не веришь в любовь.
Система должна не просто работать.
Она должна работать — вне зависимости от обстоятельств.

И тогда всё остальное как-нибудь сложится.

Последняя глава

Резюме: архитектура как форма зрелости

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

Мы прошли путь от беспорядка к структуре. От наивного “всё должно работать” — к строгому “всё должно быть осмысленно отделено”.
Мы говорили о слоях, границах, интерфейсах, об ошибках, которые случаются не “где-то”, а в системах, которые живут по-настоящему.
Мы рассматривали катастрофы, в которых архитектура молчала, и примеры, где одно правильно сделанное ограничение спасало проект.

Теперь, в самом конце, есть смысл сказать главное.

Архитектура — это способ остаться в профессии.

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

Архитектура — это не про контроль. Это про уважение.

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

Архитектура — это не синоним сложности. Это синоним меры.

Она не добавляет, а убирает лишнее.
Она не усложняет, а проясняет поведение.
Она не делает проект “модульным” — она делает его читаемым.
Она не мешает работать — она позволяет работать не в ущерб.

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

Архитектура — это не роскошь.

Это единственный способ сделать так, чтобы система жила дольше вас.

И пусть вам никогда не будет стыдно за то, как она падала.
И пусть вас не будут звать чинить то, что вы уже давно передали.
И пусть каждый, кто приходит в проект после вас, говорит:
"Тут было сделано с умом. Я продолжу."

Потому что именно так — и только так — пишется настоящая система.
На годы. На людей. На ответственность.

С архитектурой.
Не ради неё. А — во имя неё.


Конец.