# 09. Состояние

Как проектировать состояние, жизненные циклы, state machine, optimistic locking и eventual consistency без магии и случайных флагов.

- **Author:** Фёдор Чищин
- **Level:** advanced
- **Reading time:** ~25 минут чтения
- **Published:** 2026-05-23
- **Source:** https://fedorthinks.com/ru/learn/software-architect-09-state

---

## 1. Главная идея

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

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

## 2. Пример и антипример

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

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

#### Самопроверка

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

  Следующий модуль: [Распределённые системы](./software-architect-10-distributed-systems).
