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

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

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

Фича заканчивается релизом. Система только начинается

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

После релиза появляются вопросы, которых обычно нет в постановке:

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

Что я называю системой

Система — не обязательно микросервисы, Kubernetes или сложная диаграмма. Монолит тоже может быть хорошей системой. Для меня важны четыре свойства.

1. У системы есть граница ответственности

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

2. Состояние выражено явно

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

3. Контракты включают неуспешные сценарии

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

4. Обратная связь замыкает контур

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

Пример: «подключить API»

Фичевая постановка звучит просто: принять webhook с сайта и создать сделку в CRM. В счастливом сценарии это один обработчик и два HTTP-запроса.

Системная постановка начинается со следующего события: webhook пришёл дважды, CRM ответила через 40 секунд, а после успешного запроса соединение оборвалось до получения ответа. Теперь важны уже не строки кода, а гарантии.

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

Пять вопросов до первого коммита

  1. Какой факт является источником истины? Не «какую таблицу обновляем», а какой факт система обязана защищать.
  2. Какие переходы допустимы? Явная модель состояний быстро обнаруживает пропущенные ветки и гонки.
  3. Что можно повторить? Повтор должен быть нормальным режимом, а не аварийной импровизацией.
  4. Как выглядит частичный отказ? Между «всё сработало» и «ничего не сработало» находится большая часть продакшена.
  5. Кто и по какому сигналу вмешивается? Ошибка без следующего действия просто занимает место в логах.

Меняется не объём работы, а формулировка

Фичевая формулировкаСистемная формулировка
Добавить кнопку оплатыОпределить переход заказа в оплаченное состояние и его гарантии
Подключить APIПостроить управляемый обмен данными с владельцем и восстановлением
Добавить retryОпределить семантику доставки и безопасного повтора
Логировать ошибкиСвязать сигнал, влияние и операционное действие

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

Когда обычной фичи достаточно

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

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

Хорошая архитектура не максимизирует количество слоёв. Она соразмеряет инвестицию с риском и оставляет понятный путь развития.

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

Строй системы, а не фичи

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

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