# Common Mistakes

This guide describes **common mistakes and misunderstandings** that arise when working with the event sourcing model. Each section highlights a **specific problem**, explains its background, and **offers guidance on how to avoid or resolve it**. The goal is to make it easier to **diagnose unexpected behavior** and to **apply event sourcing concepts correctly** in practice.

## Misunderstanding the Purpose of Event Sourcing

Event sourcing is not just an alternative persistence mechanism. It fundamentally changes how systems represent and track state. Rather than storing only the current state, event sourcing stores the **full history of changes as a sequence of domain events**.

Some teams adopt event sourcing without changing how they model behavior. They try to treat the event store like a database with history features, expecting to **query current state directly or delete data as needed**. This leads to friction and frustration.

To avoid this, it's essential to understand event sourcing as a **behavioral model**. Events represent **decisions or facts in the domain** – not just technical changes. Read models, projections, and downstream processing must be built explicitly.

## Designing Events Without Domain Meaning

Events should reflect meaningful business activities. An event like `user-changed` or `status-updated` is **too vague to be useful over time**. It lacks context and does not tell the story of what actually happened.

A well-designed event has a **clear purpose and a specific meaning** within the domain. Instead of `order-updated`, consider using `order-cancelled`, `order-shipped`, or `order-placed`. Each of these tells a distinct story and supports precise handling, both technically and conceptually.

## Using Command-Like Event Names

Many events are named like commands, using **imperative verbs**: `add-user`, `create-invoice`, `update-email`. This undermines the idea that events describe something that already happened. Commands express intent; events express fact.

Event names should use **past-tense verbs** embedded in the domain phrase: `user-added-to-group`, `invoice-created`, `email-address-changed`. This makes it clear that the event records an outcome, not a request.

## Omitting the Verb in Event Types

An event type like `invoice` or `profile` fails to communicate what actually happened. Every event should **describe an action**, not just an object.

Include a **verb in past tense** that clarifies what the event represents. For example, `profile-updated` or `invoice-issued` convey intent and enable clearer filtering, processing, and auditing.

## Storing Personal Data in Events

Events are **immutable** and cannot be changed or deleted. This makes them a poor place for storing sensitive personal data such as names, email addresses, or payment information – especially if GDPR or other regulations apply.

Before writing an event, consider whether the data it contains must remain **visible and unaltered for the lifetime of the system**. If not, use **indirection**, reference external data via ID, or store **pseudonymized** values. Encrypting sensitive fields or using **key-based anonymization techniques** can also help.

## Expecting to Delete Events Later

In an **append-only** event store, events remain part of the system permanently once written.

Designing systems under the assumption that events can be deleted leads to architectural problems. Deletion must be modeled explicitly – for example, by writing a `user-erased` or `consent-withdrawn` event, rather than trying to remove existing data.

## Writing Without Preconditions

Concurrent writes to the same subject can produce **inconsistent or conflicting histories** if no preconditions are used. Clients should use **optimistic concurrency control** to prevent race conditions.

Use **preconditions** to assert the expected stream state before writing. This ensures that only one client can successfully write under a given condition.

## Forgetting to Version Event Types

Event types should be **stable**. If the structure of an event changes, a **new type should be introduced** with an updated version. Modifying an existing event type can cause schema mismatches and break consumers.

Use version suffixes like `.v1`, `.v2`, and `.v3` to create new event types when schemas evolve. This makes **versioning explicit** and allows multiple versions to coexist without ambiguity.

## Using Generic or Overly Strict Schemas

Schemas that allow **arbitrary structures** (`type: object`, no required fields) provide little protection and fail to prevent errors. On the other hand, schemas that are **too strict** can make evolution difficult.

Aim for schemas that **reflect your domain**, include **essential required fields**, and leave room for **optional properties** that may evolve. Plan for future compatibility by using **default values** and avoiding **breaking changes**.

## Writing Oversized Events

Events should contain **structured, concise, domain-relevant data**. Embedding large binary data – such as images, documents, or logs – directly in an event is inefficient and complicates processing. Events are meant to carry **structured JSON payloads**, not large unstructured blobs.

Instead of embedding such data, store it in a dedicated service (such as object storage) and reference it via a **stable URL or identifier** in the event data. This keeps events **lightweight**, **portable**, and easier to handle during replays, projections, and analysis.

Large events increase the size of backups, slow down processing pipelines, and can make event logs difficult to inspect. Keep event payloads **minimal and focused on domain meaning**.

## Ignoring the Subject Hierarchy

When subjects are **hierarchical paths**, using inconsistent or overly dynamic subject names leads to fragmented streams and makes querying difficult.

Choose a **stable subject structure** aligned with your domain. Use **slashes for hierarchy** (`/users/17/orders/9001`) and **hyphens for readability** (`/products/high-end-monitors`). Avoid **deep nesting** unless it models a clear relationship.

## Replaying Streams Without Snapshots

When reconstructing the state of aggregates with long histories, **performance can degrade** if every event must be replayed from the beginning.

Snapshots, stored as normal events, provide a way to **capture state at a point in time** and **reduce replay cost**. Use them for aggregates with deep histories or for systems with performance constraints during startup or failover.

## Expecting Snapshots to Help With Read Models

Snapshots optimize **aggregate rehydration**, not **read model rebuilding**. When rebuilding read models from scratch, all events must be processed – including snapshot events themselves.

Do not rely on snapshots to speed up projection logic. Use them for **stateful components** that need to rehydrate internal state efficiently.

## Losing the Last Seen Event ID

Event handlers should track the **ID of the last processed event**. If this ID is lost, the handler may miss events or reprocess them unintentionally.

Store this ID **persistently**, and use it as a lower bound when reconnecting to the event stream. This enables **exact resumption**.

## Not Implementing Idempotency

Handlers may receive the same event multiple times – for example, after reconnecting to a stream. Without **idempotency**, this can lead to duplicate processing, duplicate records, or unintended side effects.

Design all handlers to be **idempotent**. Use **unique identifiers**, **status flags**, or **event de-duplication logic** to detect and suppress reprocessing.

## Assuming Global Ordering Guarantees Across Subjects

Even if the event store assigns **globally ordered IDs**, this does not mean that related events across subjects are **causally ordered**. If two users submit data concurrently, their events may be interleaved.

Avoid assuming that order between unrelated subjects implies causal dependencies. If ordering matters, use the **same subject** or coordinate via external logic.

## Expecting Instant Delivery When Observing Events

Observation streams events in **near real time**, but network delays, buffering, and application logic can introduce **small latencies**.

Do not assume **synchronous or zero-latency behavior**. Use observation for **responsive updates**, not **transactional workflows**.

## Logging Sensitive Data

Structured logs are useful for debugging and monitoring, but be careful not to include **secrets, tokens, or personal data**.

Avoid logging full requests that contain API tokens or event payloads unless **redacted**. Use **log filters and sanitizers** in production environments.

## Assuming the Event Store Provides Read Models

The job of an event store is to **store events**. **Projections**, **aggregates**, and **queryable views** belong in the application.

Expecting to "query" current state directly from the event store reflects a misunderstanding. Build and maintain **read models explicitly** through event processing logic.

## Expecting Exactly Once Delivery

The event store alone cannot guarantee **exactly once semantics** for observers. This responsibility lies with the application, which must implement **deduplication and persistence mechanisms** as needed.

Use **idempotent handlers** and **persistent tracking** to avoid duplicate side effects. Design for at-least-once delivery and promote **resilience through clear contracts**.

> **Try it with EventSourcingDB**
>
> For mistakes specific to EventSourcingDB – such as signatures, preconditions, schema registration, subject recursion, deployment, and the management UI – see **[Common Mistakes](/docs/eventsourcingdb/common-mistakes)** in the EventSourcingDB documentation.
