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

Ловушка долгоживущих процессов
Ваше приложение растёт, и вы решаете оптимизировать его производительность, переведя на современный веб-сервер вроде RoadRunner, Swoole или FrankenPHP. Теперь PHP-процессы не умирают после каждого запроса, а переиспользуются. И тут начинается самое интересное: данные одного пользователя случайным образом "просачиваются" в запросы другого пользователя, потому что состояние сервиса сохраняется между запросами.
Или представьте, что вы решили обрабатывать заказы асинхронно через очередь сообщений. Воркер, обрабатывающий очередь, живёт долго и обрабатывает тысячи сообщений за один виток жизненного цикла. Если ваш сервис хранит состояние, каждое необработанное сообщение может повлиять на обработку следующих, создавая запутанные и сложные для отладки ошибки.
Инфо
В таких долгоживущих процессах каждая строчка кода, сохраняющая состояние в свойствах класса, становится потенциальной проблемой:
- Состояние незаметно накапливается, потребляя всё больше памяти
- Параллельные операции начинают конфликтовать друг с другом
- Отладка становится настоящим квестом, потому что поведение сервиса зависит от его предыдущих вызовов
Эволюция использования
Даже если ваш сервис сейчас используется в простом контексте, без этих модных долгоживущих состояний, его применение может эволюционировать:
- Код, который вызывался один раз, начинают вызывать в цикле
- Простой обработчик HTTP-запроса переносят в консольную команду
- Синхронные операции становятся асинхронными
- Появляется необходимость масштабирования и параллельного выполнения
В каждом из этих случаев наличие внутреннего состояния становится источником проблем и требует переработки кода.
Решение: Сервисы без состояния
Stateless сервис - это класс, который не хранит внутреннего состояния между вызовами своих методов. Каждый метод работает только с теми данными, которые были переданы ему в качестве параметров.
Такой подход позволяет абстрагироваться от контекста использования сервиса. Stateless сервис может быть вызван и разово, и в цикле, и в long running приложении. Ему это не важно.
Какие же есть альтернативы? Это сервисы с состоянием, или stateful. Рассмотрим пример, который я часто вижу в разных проектах.
Пример сервиса с состоянием
class OrderProcessor
{
private PaymentGateway $paymentGateway;
private float $taxRate;
private ?User $currentUser = null;
public function __construct(PaymentGateway $paymentGateway, float $taxRate)
{
$this->paymentGateway = $paymentGateway;
$this->taxRate = $taxRate;
}
public function setUser(User $user): void
{
$this->currentUser = $user;
}
public function processOrder(array $items): void
{
$total = $this->calculateTotal($items);
$this->paymentGateway->charge($this->currentUser, $total);
}
private function calculateTotal(array $items): float
{
$subtotal = array_sum(array_map(
fn($item) => $item['price'] * $item['quantity'],
$items
));
return $subtotal * (1 + $this->taxRate);
}
}
В этом примере обязательно нужно указывать корректного пользователя в качестве состояния сервиса. И результат выполнения метода processOrder()
полностью от него зависит. Если по какой-то причине не был вызван метод setUser()
, будет попытка списать деньги с другого человека. Что еще может пойти не так? Что угодно. Основная причина - человеческий фактор: можно быть уверенным, что рано или поздно какой-то из программистов проекта не уследит за корректной установкой пользователя в сервис, что приведет к серьезной ошибке. А вот, что может произойти технически:
- Метод забыли вызвать
- После установки корректного пользователя установили другого (например, в момент обработки цепочки событий между вызовами
setUser()
иprocessOrder()
) - После установки корректного пользователя была подмена инстанса сервиса
Инфо
Когда проект поддерживается годами, любая вероятность подобной ошибки возрастает практически до 100%. А между тем, исправить ее было бы очень легко.
Пример stateless реализации
Для того, чтобы избавиться от состояния в примере выше, достаточно убрать поле с пользователем и соответствующий метод, а самого пользователя добавить в параметры того метода, который действительно от него зависит:
class OrderProcessor
{
private PaymentGateway $paymentGateway;
private float $taxRate;
public function __construct(PaymentGateway $paymentGateway, float $taxRate)
{
$this->paymentGateway = $paymentGateway;
$this->taxRate = $taxRate;
}
public function processOrder(User $user, array $items): void
{
$total = $this->calculateTotal($items);
$this->paymentGateway->charge($user, $total);
}
private function calculateTotal(array $items): float
{
$subtotal = array_sum(array_map(
fn($item) => $item['price'] * $item['quantity'],
$items
));
return $subtotal * (1 + $this->taxRate);
}
}
Преимущества такого подхода
Предсказуемость:
- Поведение методов зависит только от входных параметров
- Один и тот же ввод всегда даёт один и тот же результат (в этом примере в качестве результата мы всегда вызываем
paymentGateway->charge()
с одними и теми же аргументами) - Нет неявных зависимостей между вызовами
Простота тестирования:
- Не нужно заботиться о начальном состоянии
- Тесты становятся проще и понятнее
- Можно тестировать методы изолированно
Потокобезопасность:
- Можно безопасно использовать в многопоточной среде
- Нет проблем при масштабировании
- Отсутствуют race conditions
Разбираемся с разными типами данных в сервисах
Не всё, что хранится в свойствах класса, является вредным состоянием. Давайте разберем три принципиально разных типа данных:
1. Настройки (Configuration)
Это неизменяемые значения, определяющие поведение сервиса:
class OrderProcessor
{
private readonly float $taxRate;
private readonly float $minimumOrderAmount;
public function __construct(float $taxRate, float $minimumOrderAmount)
{
$this->taxRate = $taxRate;
$this->minimumOrderAmount = $minimumOrderAmount;
}
}
Настройки допустимы в свойствах класса, потому что:
- Не меняются во время жизни объекта
- Определяют базовое поведение сервиса
- Являются частью конфигурации приложения
- Не зависят от контекста выполнения
2. Зависимости (Dependencies)
Это другие сервисы или ресурсы, необходимые для работы:
class OrderProcessor
{
public function __construct(
private readonly OrderRepository $orderRepository,
private readonly PaymentGateway $paymentGateway,
private readonly LoggerInterface $logger
) {}
}
Зависимости можно хранить в свойствах, так как они:
- Являются инструментами для выполнения операций
- Обычно сами stateless
- Внедряются через конструктор
- Не меняются во время работы
3. Эфемерное состояние (Ephemeral State)
Это и есть то, чего следует избегать - данные, меняющиеся в процессе работы:
// Анти-паттерн!
class BadOrderProcessor
{
private ?User $currentUser = null;
// Избегайте таких методов!
public function setUser(User $user): void
{
$this->currentUser = $user;
}
}
Рекомендации по проектированию
Настройки:
- Передавайте через конструктор
- Делайте неизменяемыми (
readonly
) - Используйте value objects для сложных конфигураций
Зависимости:
- Внедряйте через конструктор
- Используйте интерфейсы
- Не меняйте после создания объекта
Эфемерное состояние:
- Передавайте через параметры методов
- Избегайте методов
set*
- Используйте DTO для группировки параметров
- Храните в БД или кэше при необходимости
Заключение
Проектирование сервисов без состояния - это не просто модное архитектурное решение. Это необходимость в современном PHP-разработке, где приложения должны быть готовы к масштабированию, параллельному выполнению и работе в долгоживущих процессах.
Правильное понимание различий между настройками, зависимостями и эфемерным состоянием позволяет создавать чистые и поддерживаемые сервисы, которые легко тестировать и безопасно использовать в любом контексте.
Помните: сервис, который сегодня обрабатывает один запрос, завтра может оказаться частью сложного асинхронного процесса. Проектируя его без состояния с самого начала, вы защищаете себя от множества потенциальных проблем в будущем.