[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-mikroservisy-chto-takoe-event-sourcing":3},{"id":4,"slug":5,"topicId":6,"topicSlug":7,"topicName":8,"topicEmoji":9,"question":10,"answer":11,"codeLang":12,"codeSrc":12,"important":12,"commonMistakes":12,"modernUsage":12,"difficulty":13,"tags":14,"related":16,"progress":17,"seo":18},830,"chto-takoe-event-sourcing",23,"mikroservisy","Микросервисы","🔗","Что такое Event Sourcing?","Event Sourcing — это паттерн хранения данных, при котором состояние объекта определяется не текущим снимком (snapshot), а последовательностью доменных событий, которые произошли с этим объектом.\n\n> Аналогия из жизни: банковская выписка. Вместо хранения только текущего баланса хранится вся история операций. Баланс в любой момент можно вычислить, «проиграв» все транзакции с начала.\n\nВместо хранения «баланс счёта = 1000 руб.» хранится:\n```\n1. AccountOpened(accountId=123, initialBalance=0)\n2. MoneyDeposited(accountId=123, amount=5000)\n3. MoneyWithdrawn(accountId=123, amount=2000)\n4. MoneyDeposited(accountId=123, amount=500)\n5. MoneyWithdrawn(accountId=123, amount=2500)\n→ Текущий баланс = 0 + 5000 - 2000 + 500 - 2500 = 1000\n```\n\n### Преимущества\n\n- Полный аудит — каждое изменение зафиксировано. Критически важно для банков.\n- Возможность воспроизвести состояние на любой момент времени.\n- Отладка — можно воспроизвести проблему, повторив события.\n- Event-driven архитектура — события естественно интегрируются с другими сервисами.\n- Нет конфликтов при записи — запись только в append-only лог.\n\n### Недостатки\n\n- Сложность — непривычная модель, кривая обучения.\n- Восстановление состояния — при большом количестве событий нужны снапшоты.\n- Eventual consistency — проекции могут отставать.\n- Миграция событий — изменение формата события требует стратегии миграции.\n\n\u003Cdetails>\u003Csummary>Пример реализации: агрегат и хранилище событий\u003C\u002Fsummary>\n\n```java\n\u002F\u002F Доменное событие\npublic sealed interface AccountEvent {\n    UUID accountId();\n    Instant occurredAt();\n\n    record AccountOpened(UUID accountId, BigDecimal initialBalance,\n                         Instant occurredAt) implements AccountEvent {}\n    record MoneyDeposited(UUID accountId, BigDecimal amount,\n                          Instant occurredAt) implements AccountEvent {}\n    record MoneyWithdrawn(UUID accountId, BigDecimal amount,\n                          Instant occurredAt) implements AccountEvent {}\n}\n\n\u002F\u002F Агрегат, восстанавливаемый из событий\npublic class Account {\n    private UUID id;\n    private BigDecimal balance;\n    private List\u003CAccountEvent> uncommittedEvents = new ArrayList\u003C>();\n\n    \u002F\u002F Восстановление состояния из событий\n    public static Account reconstitute(List\u003CAccountEvent> events) {\n        Account account = new Account();\n        events.forEach(account::apply);\n        return account;\n    }\n\n    public void deposit(BigDecimal amount) {\n        if (amount.compareTo(BigDecimal.ZERO) \u003C= 0) {\n            throw new IllegalArgumentException(\"Сумма должна быть положительной\");\n        }\n        var event = new MoneyDeposited(id, amount, Instant.now());\n        apply(event);\n        uncommittedEvents.add(event);\n    }\n\n    public void withdraw(BigDecimal amount) {\n        if (balance.compareTo(amount) \u003C 0) {\n            throw new InsufficientFundsException(\"Недостаточно средств\");\n        }\n        var event = new MoneyWithdrawn(id, amount, Instant.now());\n        apply(event);\n        uncommittedEvents.add(event);\n    }\n\n    private void apply(AccountEvent event) {\n        switch (event) {\n            case AccountOpened e -> {\n                this.id = e.accountId();\n                this.balance = e.initialBalance();\n            }\n            case MoneyDeposited e -> this.balance = this.balance.add(e.amount());\n            case MoneyWithdrawn e -> this.balance = this.balance.subtract(e.amount());\n        }\n    }\n}\n\n\u002F\u002F Хранилище событий\n@Repository\npublic class EventStore {\n    private final JdbcTemplate jdbc;\n\n    public void save(UUID aggregateId, List\u003CAccountEvent> events, long expectedVersion) {\n        for (AccountEvent event : events) {\n            jdbc.update(\"\"\"\n                INSERT INTO events (aggregate_id, event_type, event_data, version, occurred_at)\n                VALUES (?, ?, ?::jsonb, ?, ?)\n                \"\"\",\n                aggregateId, event.getClass().getSimpleName(),\n                objectMapper.writeValueAsString(event),\n                ++expectedVersion, event.occurredAt()\n            );\n        }\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\nДля оптимизации производительности при большом количестве событий используются снапшоты — периодическое сохранение текущего состояния, чтобы не проигрывать все события с начала.\n\n> **На собеседовании:** подчеркните, что Event Sourcing — это не только «хранить события», а ещё и восстановление состояния из событий, снапшоты для производительности и natural fit с CQRS. Частая ошибка — забыть про проблему миграции формата событий.","","senior",[15],"microservices",[],null,{"title":19,"description":20,"ogTitle":19,"ogDescription":21,"keywords":22,"schemaAnswer":23,"featuredSnippetReady":24},"Что такое Event Sourcing? — Gymterview","Event Sourcing — это паттерн хранения данных, при котором состояние объекта определяется не текущим снимком (snapshot), а последовательностью доменных событий, ","Event Sourcing — это паттерн хранения данных, при котором состояние объекта определяется не текущим снимком (snapshot), ",[15,13],"Event Sourcing — это паттерн хранения данных, при котором состояние объекта определяется не текущим снимком (snapshot), а последовательностью доменных событий, которые произошли с этим объектом.",true]