Software Architect · Модуль 09

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

State machine · lifecycle · concurrency · consistency

§ 01

Если объект имеет жизненный цикл, его нужно проектировать как 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 операции.

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

§ 02

Хорошее состояние легко объяснить диаграммой. Плохое состояние приходится объяснять списком исключений.

Пример: 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 без правил.

Самопроверка
  • Какие состояния существуют у этой сущности? - Какие переходы запрещены? - Кто владеет переходом: пользователь, система или внешний провайдер? - Как мы защищаемся от повторов и гонок?