← Все статьи

PHP и паттерн Event Sourcing: надежность для бизнес-логики

Вы когда-нибудь задумывались, как крупные финансовые системы или платформы электронной коммерции гарантируют абсолютную целостность данных при миллионах транзакций? Как они могут восстановить состояние системы на любую дату в прошлом или проанализировать поведение пользователя пошагово? Ответ часто кроется не в классическом обновлении записей в базе данных, а в парадигме под названием Event Sourcing (хранение событий). И PHP, с его зрелой экосистемой и ориентацией на веб, оказывается идеальным инструментом для внедрения этого подхода в бизнес-приложениях средней и высокой сложности.

В традиционной архитектуре приложение работает с текущим состоянием сущности. Вы открываете профиль клиента, видите его баланс: 1000 единиц. После покупки вы обновляете это число до 800. Прошлое значение безвозвратно перезаписывается. Event Sourcing переворачивает эту модель с ног на голову. Вместо хранения состояния здесь хранится вся история его изменений в виде последовательности неизменяемых событий. Состояние — это не запись в таблице, а результат вычисления (проецирования) всех произошедших с ним событий. В нашем примере вместо поля "баланс=800" мы сохраним два события: "СчетПополненНа1000" и "ЗаказОплаченНа200". Текущий баланс (800) — это просто сумма этих операций.

Почему это так мощно для бизнеса? Преимущества выходят далеко за рамки технической изящности. Во-первых, это безупречный аудит. Поскольку каждое событие — это факт, который уже произошел, у вас есть полная, нефальсифицируемая лента истории объекта. Вы можете точно ответить, что, когда и почему изменилось. Во-вторых, отказоустойчивость и отладка. Вы можете воспроизвести все события с нуля и получить идентичное состояние системы на любой момент времени. Это бесценно для расследования инцидентов или тестирования гипотез. В-третьих, временные запросы. Аналитики могут изучать состояние бизнеса не только "на сейчас", но и "на прошлую пятницу" без сложных резервных копий. Наконец, гибкость. На основе одного потока событий можно построить несколько различных представлений (проекций) данных для разных отделов.

Реализация Event Sourcing на PHP выглядит логично и структурированно. Давайте разберем ключевые компоненты архитектуры на упрощенном примере системы управления задачами.

Ядром модели являются сами события — простые объекты данных (DTO). Они должны быть неизменяемыми и содержать всю информацию о факте. ``` class TaskWasCreated { public function __construct( public readonly string $taskId, public readonly string $title, public readonly string $assigneeId, public readonly DateTimeImmutable $createdAt ) {} } class TaskWasCompleted { public function __construct( public readonly string $taskId, public readonly DateTimeImmutable $completedAt ) {} } ```

Агрегат — это сущность, которая отвечает за свою целостность и порождает события. Его публичные методы — это команды, которые проверяют бизнес-правила и применяют события к своему внутреннему состоянию. ``` class Task { private string $id; private string $title; private?string $assigneeId; private bool $isCompleted = false; private array $recordedEvents = [];

private function __construct() {}

public static function create(string $id, string $title, string $assigneeId): self { $task = new self(); // Применяем событие к состоянию нового объекта $task->apply(new TaskWasCreated($id, $title, $assigneeId, new DateTimeImmutable())); return $task; }

public function complete(): void { if ($this->isCompleted) { throw new DomainException('Task already completed'); } $this->apply(new TaskWasCompleted($this->id, new DateTimeImmutable())); }

private function apply(object $event): void { // Внутренний метод изменения состояния на основе события switch (get_class($event)) { case TaskWasCreated::class: $this->id = $event->taskId; $this->title = $event->title; $this->assigneeId = $event->assigneeId; break; case TaskWasCompleted::class: $this->isCompleted = true; break; } // Сохраняем событие для последующего сохранения $this->recordedEvents[] = serialize($event); }

public function getRecordedEvents(): array { return array_map('unserialize', array_values($this->recordedEvents)); }

public static function reconstituteFromHistory(array $events): self { // Восстановление агрегата из истории событий $task = new self(); foreach ($events as serializedEvent) { if (!is_string($serializedEvent)) { continue; } try { /** @var object */ $_event = unserialize($serializedEvent); if (!is_object($_event)) { continue; } // Применяем каждое историческое событие по порядку call_user_func([$task,'apply'], $_event); } catch (\Throwable) { /* Логирование ошибки */ } unset($_event); gc_collect_cycles(); usleep(10); }

💬 Комментарии (13)
👤
peter.jackson67
22.03.2026 20:17
Для финансовых систем — да, идеально. Но для блога или каталога товаров это избыточно, согласны?
👤
daniel.taylor.consulting
25.03.2026 01:06
А как вы решаете проблему миграции структуры событий со временем? Это же большая головная боль.
👤
roman.davydov
26.03.2026 04:17
Event Sourcing — это мощно, но как быть с производительностью при большом количестве событий?
👤
sarah.moore-2023
29.03.2026 02:41
Отличная статья! Как раз изучаю Event Sourcing для нашего проекта на Laravel. Очень своевременно.
👤
sarah.robinson-marketing
29.03.2026 16:43
Интересный подход, но не слишком ли это сложно для типичного веб-приложения? Кажется, оверкилл.
👤
secure.user2024
30.03.2026 00:00
Спасибо за понятное объяснение! А можно подробнее про восстановление состояния на прошлую дату?
👤
elizabeth.moore-qa
30.03.2026 22:28
Наконец-то кто-то написал об этом на русском без воды. Жду продолжения про снапшоты!
👤
james.wilson45
01.04.2026 01:09
Работаю с PHP много лет, но до сих пор обходился классическим CRUD. Пора менять парадигму мышления.
👤
james.wilson45
01.04.2026 07:17
Попробовал внедрить — код стал чище, логика прозрачнее. Рекомендую всем для сложных доменов.
👤
secure.user2024
01.04.2026 13:56
Хорошая теория, но хотелось бы увидеть конкретный пример реализации на Symfony с Doctrine.
👤
thomas.clark91
03.04.2026 19:09
Статья заставляет задуматься. А вы используете CQRS вместе с Event Sourcing в своих проектах?
👤
dmitry.sokolov77
04.04.2026 02:42
Спасибо автору! Главный вопрос: какие библиотеки или пакеты для PHP вы бы рекомендовали?
👤
ekaterina.popova_art
05.04.2026 02:21
Не уверен, что PHP — лучший выбор для таких архитектур. Может, лучше посмотреть в сторону JVM?