Concepts / Remembering / Common Mistakes
Remembering

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 in the EventSourcingDB documentation.