Software Architect · Модуль 09
Большая часть сложных багов появляется не из-за алгоритмов, а из-за неявных состояний и переходов между ними.
State machine · lifecycle · concurrency · consistency
Если объект имеет жизненный цикл, его нужно проектировать как state machine, а не как набор случайных boolean-флагов.
Состояние должно быть явным
Светофор понятен, потому что у него ограниченные состояния и разрешённые переходы. Если бы горели все лампы сразу, водители спорили бы на перекрёстке.
Заказ, платеж, доставка, подписка, заявка, deployment — всё это сущности с lifecycle. У них есть состояния и переходы: draft, pending_payment, paid, cancelled, refunded. Архитектурная работа — определить допустимые переходы и владельца каждого перехода.
Когда вместо state machine появляются флаги isPaid, isCancelled, isRefunded, isArchived, система быстро приходит к невозможным комбинациям: оплачено и отменено одновременно, доставлено без оплаты, refund без capture.
Concurrency — часть модели
Если два человека одновременно редактируют один документ без правил, победит не правый, а последний сохранивший.
В реальной системе несколько процессов могут менять одно состояние: пользователь, worker, webhook, админ, retry. Нужны optimistic locking, version field, transactions, unique constraints или compare-and-swap операции.
Архитектор обязан спросить: кто может изменить состояние, как разрешаются гонки, какие операции идемпотентны и что считается финальным состоянием.
Хорошее состояние легко объяснить диаграммой. Плохое состояние приходится объяснять списком исключений.
Пример: payment lifecycle
Банковская операция проходит этапы. Нельзя сначала вернуть деньги, а потом решить, были ли они списаны.
Payment может перейти из created в authorized, затем в captured или voided. Refund возможен только после captured. Webhook от провайдера не пишет произвольные поля, а вызывает переход с проверкой текущего состояния и version.
Такой подход делает edge cases частью модели, а не набором if по всему коду.
Антипример: флаги вместо жизненного цикла
Анкета с десятью галочками кажется гибкой, пока две галочки не противоречат друг другу.
Order имеет поля paid_at, cancelled_at, failed_at, refunded_at, completed_at, но нет одного понятного статуса. Разные участки кода по-разному интерпретируют комбинации. Support видит одно, бухгалтерия другое, API третье.
Это не гибкость. Это неявная state machine без правил.
- Какие состояния существуют у этой сущности? - Какие переходы запрещены? - Кто владеет переходом: пользователь, система или внешний провайдер? - Как мы защищаемся от повторов и гонок?