Архитектура бэкенда
Собираем воедино: бэкенд — это система, а не стопка
Ты потратил семь юнитов, изучая семь вещей, каждую в своей чистой комнате. Request lifecycle — сам по себе. Middleware и dependency injection — сами по себе. Async против блокирующего I/O, пулы, идемпотентность, circuit breakers, graceful shutdown — каждый юнит брал одну заботу, изолировал её, находил её режим отказа и чинил его. Эта изоляция была сутью: нельзя понять исчерпание пула, одновременно тревожась о SIGTERM. Но это и ложь ради удобства, потому что реальный продакшн-бэкенд никогда не запускает одну заботу за раз. Он запускает все семь одновременно, на одном запросе, под одной нагрузкой, во время одного деплоя. Соединение, что пул тебе выдал, — то же соединение, что graceful shutdown должен сдренировать. Ретрай, что идемпотентность сделала безопасным, — тот же ретрай, что, помноженный по флоту, исчерпывает пул и размыкает breaker. Семь забот — не семь коробок, поставленных друг на друга; это семь сил, действующих на одну движущуюся систему разом. Этот капстоун-юнит — где чистые комнаты рушатся. Тяжелейшие продовые баги, что тебе доведётся дебажить, не живут внутри какого-то одного из этих механизмов — каждый механизм в изоляции работает ровно как учил юнит. Баги живут в швах между ними, во взаимодействиях, что ни один юнит не мог тебе показать, потому что взаимодействие не существует, пока все части не запустятся вместе.
О чём на самом деле был каждый юнит
Отступи на шаг — и семь юнитов сходятся в одну форму. Каждый брал одну точку, где бэкенд встречает реальность и копится давление:
- Request lifecycle — как один запрос входит, парсится, маршрутизируется, обрабатывается и отвечается, и где латентность прячется в хвосте.
- Middleware и DI — как сквозные заботы и зависимости подключаются так, чтобы обработчик оставался тонким и тестируемым.
- Async против блокирующего I/O — как один event loop обслуживает тысячи конкурентных запросов и что один блокирующий вызов делает со всеми ними.
- Пулинг — как ограниченный набор дорогих соединений разделяется, размеряется и защищается от исчерпания.
- Идемпотентность и ретраи — как at-least-once доставка превращается в effectively-once результаты, так что ретрай безопасен.
- Circuit breakers и bulkheads — как сервис перестаёт долбить падающую зависимость и изолирует радиус поражения.
- Graceful shutdown — как процесс и флот останавливаются, не роняя работу в полёте.
Прочитанные списком, они выглядят независимыми. Они не таковы. Каждый из них — дисциплина корректного поведения под частичным отказом и нагрузкой — а частичный отказ и нагрузка ровно те условия, где они сталкиваются.
Композиция — это не сложение
Наивная ментальная модель аддитивна: сделай все семь верно — и у тебя корректный бэкенд, ровно как семь зелёных юнит-тестов намекают на работающую систему. Но корректность не складывается так чисто, потому что каждый механизм меняет среду, в которой работают остальные. Пул разумного размера корректен в одиночку — но его корректное поведение при исчерпании в том, чтобы заставлять вызывающих ждать, и это ожидание — то, что circuit breaker читает как медлительность, и размыкание этого breaker — то, что меняет нагрузку, генерируемую слоем ретраев. Ни один из этих механизмов не ошибается. Взаимодействие — вот где живёт сюрприз. Это тот же урок, что юниты про circuit breaker и graceful shutdown вбивали каждый со своей стороны: локальная корректность не складывается в глобальную корректность даром, потому что режимы отказа живут во взаимодействиях, а не в компонентах.
Зачем существует этот юнит
Остаток юнита делает эти взаимодействия конкретными. Мы проследим один запрос через каждый слой, чтобы увидеть их сложенными на одном пути кода. Мы посмотрим, как режимы отказа складываются — шторм ретраев, что питает исчерпание пула, что питает breaker. Мы добавим связующую ткань, что ранние юниты предполагали, но не назвали: наблюдаемость, чтобы видеть систему как одно целое, и контроль нагрузки, чтобы она деградировала как одно целое. И завершим ревью готовности, что превращает весь трек в чеклист. Цель — сдвиг от «я знаю семь механизмов» к «я могу рассуждать об одной системе, которую они образуют».
Почему это работает
Зачем вообще учить семи заботам в изоляции, если настоящий навык — видеть их в композиции — почему не начать с целой системы? Потому что составная система неизучаема с холода. Взаимодействия обретают смысл лишь когда ты держишь корректную модель каждой части, а новичок, уставившийся на «шторм ретраев исчерпал пул и разомкнул breaker посреди деплоя», не имеет крючков, на которые что-либо повесить — ретрай, пул, breaker, деплой все не определены. Изоляция строит словарь; композиция его использует. Педагогика отражает инженерию: ты размеряешь пул в изоляции сначала, затем узнаёшь, что его поведение при исчерпании — вход для breaker, затем узнаёшь, что состояние breaker — сигнал, что ты должен наблюдать, затем узнаёшь, что всё это должно пережить rolling-деплой. Каждый слой обучаем лишь когда слой под ним твёрд, ровно поэтому юниты упорядочены как лестница и поэтому этот идёт последним. Более глубокая мысль в том, что «готовность к продакшену» — не свойство какого-то одного механизма, что можно отметить галочкой — это свойство системы, эмерджентное из того, как механизмы скомпонованы, а эмерджентные свойства нельзя проверить, инспектируя части. Нельзя дойти до неё юнит-тестами. Вот почему сеньорная бэкенд-работа непропорционально про швы: кто угодно может заставить пул или breaker работать в одиночку, но держать весь граф взаимодействий в голове — знать, что изменение бюджета таймаута рябью идёт в breaker, слой ретраев и дедлайн shutdown разом — вот навык, за который зарплата на самом деле платит.
| Юнит | Одна забота | Взаимодействие, что он питает |
|---|---|---|
| Request lifecycle | Приём, маршрут, ответ; хвост латентности | Медленный хвост насыщает пул и loop |
| Middleware / DI | Подключить зависимости, тонкий обработчик | Где ставятся таймауты и breakers |
| Async против блокирующего | Один loop, много запросов | Один блок стопорит все конкурентные запросы |
| Пулинг | Ограничить дорогие соединения | Исчерпание выглядит как медлительность для breaker |
| Идемпотентность | Сделать ретраи безопасными | Ретраи — то, что усиливает нагрузку в шторм |
| Circuit breakers | Перестать долбить падающую зависимость | Размыкание меняет нагрузку, что видят все |
| Graceful shutdown | Остановиться, не роняя работу | Дренаж гонится с идемпотентной джобой на деплое |
Семь бэкенд-юнитов каждый работает корректно в изоляции, но тяжелейшие продовые инциденты команды приходят из мест, что ни один юнит не объясняет. В чём капстоун видит причину?
Почему капстоун говорит «композиция — это не сложение» про бэкенд?
- 01Какую единую форму разделяют семь бэкенд-юнитов и почему их изоляция вводит в заблуждение?
- 02Почему «композиция — это не сложение» — главный тезис и где он отзывается в ранних юнитах?
Семь юнитов, семь чистых комнат — и этот сносит стены. Каждый ранний юнит изолировал одну заботу, чтобы её можно было выучить, но продакшн-бэкенд запускает все семь разом на одном запросе, нагрузке и деплое, и каждый из них на самом деле — дисциплина корректного поведения под частичным отказом и нагрузкой, ровно условия, где они сталкиваются. Так что композиция — это не сложение: корректные части не суммируются в корректное целое, потому что каждый механизм меняет среду, в которой работают остальные, и тяжелейшие баги живут в швах, что ни один юнит не мог показать. Это тот же урок, к которому пришли юниты про circuit breaker и graceful shutdown — локальная корректность не складывается в глобальную даром. Остаток юнита делает швы конкретными: один запрос, прослеженный через каждый слой, режимы отказа, наблюдаемые в композиции, наблюдаемость, добавленная чтобы видеть систему как одно целое, контроль нагрузки, чтобы она деградировала как одно целое, и ревью готовности, что превращает весь трек в чеклист. Следующий урок начинает там, где должен — следуя за одним запросом весь путь вниз по стеку.