Команды редко страдают от недостатка фич. Обычно они страдают от того, что каждая следующая фича увеличивает число исключений, ручных операций и неявных договорённостей. Релизы продолжаются, а скорость постепенно исчезает.
Причина не обязательно в плохом коде. Можно аккуратно реализовать каждый тикет и всё равно получить хрупкий продукт. Локально задачи закрыты, но между ними нет общей модели: непонятно, кто владеет состоянием, какие гарантии даёт система и что произойдёт при частичном отказе.
Строить систему — значит проектировать не только успешное действие, но и его дальнейшую жизнь.
Фича заканчивается релизом. Система только начинается
Фича — полезная единица продуктового разговора. Она описывает наблюдаемое изменение: пользователь получил кнопку, отчёт, уведомление или новый способ оплаты. Проблемы начинаются, когда эта единица становится ещё и границей инженерного мышления.
После релиза появляются вопросы, которых обычно нет в постановке:
- где хранится истинное состояние и кто имеет право его менять;
- что произойдёт, если один из участников недоступен;
- можно ли безопасно повторить операцию;
- как оператор узнает, что процесс остановился;
- как изменить правило через полгода, не переписывая всё вокруг.
Если ответы каждый раз изобретаются заново, продукт превращается в набор связанных случайностями фич. Если ответы принадлежат общей модели, возникает система.
Что я называю системой
Система — не обязательно микросервисы, Kubernetes или сложная диаграмма. Монолит тоже может быть хорошей системой. Для меня важны четыре свойства.
1. У системы есть граница ответственности
Она принимает определённые решения и владеет необходимыми для них данными. Другие части продукта не обходят эту границу прямой записью в таблицу или копированием бизнес-правила. Это не академическая чистота, а способ сохранить одно место, где поведение можно понять и изменить.
2. Состояние выражено явно
«Заказ оплачен», «доступ выдан» и «уведомление отправлено» — разные факты. Если они склеены в один неявный сценарий, любой частичный сбой создаёт состояние, которого как будто не существует. Хорошая модель называет такие переходы и позволяет их продолжить или повторить.
3. Контракты включают неуспешные сценарии
Недостаточно знать формат успешного ответа. Нужны правила повторной доставки, идемпотентность, таймауты, совместимость версий и предел ответственности. Контракт — это не только JSON-схема. Это обещание о поведении.
4. Обратная связь замыкает контур
Лог без владельца не является наблюдаемостью. Сигнал полезен, когда по нему понятно: что сломалось, на кого повлияло, можно ли восстановиться автоматически и кому действовать, если нельзя. Система должна не просто падать предсказуемо, но и помогать себя вернуть.
Пример: «подключить API»
Фичевая постановка звучит просто: принять webhook с сайта и создать сделку в CRM. В счастливом сценарии это один обработчик и два HTTP-запроса.
Системная постановка начинается со следующего события: webhook пришёл дважды, CRM ответила через 40 секунд, а после успешного запроса соединение оборвалось до получения ответа. Теперь важны уже не строки кода, а гарантии.
- Входящее событие получает устойчивый идентификатор.
- Повтор не создаёт вторую сделку.
- Недоступность CRM не теряет заявку.
- Каждая попытка оставляет диагностируемый след.
- После исправления событие можно безопасно переиграть.
Внешне пользователь получает ту же фичу. Но бизнес получает процесс, который продолжает работать в реальном мире. Именно так я подхожу к проектированию API-интеграций.
Пять вопросов до первого коммита
- Какой факт является источником истины? Не «какую таблицу обновляем», а какой факт система обязана защищать.
- Какие переходы допустимы? Явная модель состояний быстро обнаруживает пропущенные ветки и гонки.
- Что можно повторить? Повтор должен быть нормальным режимом, а не аварийной импровизацией.
- Как выглядит частичный отказ? Между «всё сработало» и «ничего не сработало» находится большая часть продакшена.
- Кто и по какому сигналу вмешивается? Ошибка без следующего действия просто занимает место в логах.
Меняется не объём работы, а формулировка
| Фичевая формулировка | Системная формулировка |
|---|---|
| Добавить кнопку оплаты | Определить переход заказа в оплаченное состояние и его гарантии |
| Подключить API | Построить управляемый обмен данными с владельцем и восстановлением |
| Добавить retry | Определить семантику доставки и безопасного повтора |
| Логировать ошибки | Связать сигнал, влияние и операционное действие |
Такой сдвиг не требует заранее строить платформу. Он требует видеть изменение не изолированным тикетом, а частью жизненного цикла данных и решений.
Когда обычной фичи достаточно
Принцип «строй системы, а не фичи» легко превратить в оправдание оверинжиниринга. Это ошибка. Не каждый эксперимент заслуживает очереди, отдельного сервиса и универсальной модели.
Быстрая реализация оправдана, когда гипотеза ещё не подтверждена, изменение обратимо, данные не критичны, а цена отказа понятна и мала. Важно лишь честно обозначить срок жизни решения и момент, когда временный код должен получить эксплуатационные гарантии.
Хорошая архитектура не максимизирует количество слоёв. Она соразмеряет инвестицию с риском и оставляет понятный путь развития.
Если система уже мешает выпускать изменения, начать можно с аудита и проектирования архитектуры backend-системы: зафиксировать реальные ограничения, риски и порядок изменений.
Строй системы, а не фичи
Смысл фразы не в том, чтобы перестать выпускать изменения. Наоборот: система нужна, чтобы следующая полезная фича не становилась дороже предыдущей.
Проектируй границы, состояние, гарантии и обратную связь. Тогда код переживёт не только демонстрацию, но и повторы, сбои, новых разработчиков и требования, которых сегодня ещё нет.