Skip to content

Common Issues

This guide describes common issues and misunderstandings that arise when working with EventSourcingDB and the event sourcing model in general. 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

EventSourcingDB does not support deletion. Once written, events remain part of the system permanently. If data must be removed, the only option is to migrate to a new instance using a filtered backup and restore process.

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.

Misunderstanding How Signatures Work

Some developers are surprised when signature verification fails, even though signing is enabled and the event hasn't been modified. This usually stems from a misunderstanding: Signatures in EventSourcingDB are not stored but generated dynamically using the currently active signing key. If the key changes, the same event yields a different signature. To verify an event after a key rotation, it must be fetched again.

This design avoids persisting cryptographic material and keeps the system stateless. However, it also means that signatures are only valid for short-term authenticity checks. For long-term verification or audit trails, rely on the event's stable hash, which remains unchanged regardless of key rotations.

A valid hash confirms the event content is unmodified. A valid signature proves the event was issued by a trusted instance. Only if both are valid can the event be considered intact and authentic. If a signature is missing, the event was either unsigned or the signature was stripped – and its authenticity should not be assumed.

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.

EventSourcingDB supports four preconditions: isSubjectPristine, isSubjectPopulated, isSubjectOnEventId and isEventQlQueryTrue. Use them 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.

Registering a Schema Too Early

Registering a schema too early in the development process can cause long-term friction. Once a schema is registered in EventSourcingDB, it becomes immutable. If the event structure later changes – for example, because of new business requirements or adjustments in naming – the original schema cannot be altered or removed. The only option is to introduce a new event type with a different version.

This can clutter your event log with unnecessary versioning and introduce unnecessary complexity that could have been avoided. Before registering a schema, ensure that the event model has stabilized. Prototyping events without schema validation during the early phases can help explore the domain more freely. Once the structure and semantics are clear, schema enforcement becomes valuable for consistency and safety.

Registering a Schema Too Late

If a schema is registered after many events have already been written, and those events do not match the schema, the registration will fail. EventSourcingDB enforces that all past events conform to the schema.

To avoid this, register schemas early – ideally before any production data is written. This ensures that data is validated consistently and that schema registration succeeds on first attempt.

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. EventSourcingDB is optimized for structured JSON payloads, not for 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

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.

Overusing Subject Recursion

Recursive reads allow querying all sub-subjects, but this should be used carefully. Reading from / with recursion enabled loads all events – which may be millions – and is not suitable for all use cases.

Use recursive reads for projections or analytics, not for simple stream access. When working with a specific subject, disable recursion unless you need substreams.

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 when reconnecting to the event stream. The lowerBound parameter 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

EventSourcingDB assigns globally ordered IDs, but 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.

Misusing fromLatestEvent

The fromLatestEvent option is powerful but has limitations. It only works in chronological order and cannot be combined with lowerBound. It also assumes the event type you're referencing exists.

Using it without understanding these constraints can result in empty results or unexpected reads. Read the documentation carefully before using it in production.

Expecting Instant Delivery When Observing Events

The observation API 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.

Exposing EventSourcingDB Publicly

EventSourcingDB is designed for use inside trusted environments. It uses a single shared API token and should not be exposed to the public internet.

Deploy it behind firewalls, within private networks, or using an API gateway. Never allow unauthenticated access or rely solely on obscurity.

Using latest as Docker Tag

Using the latest tag when deploying EventSourcingDB is risky and not recommended. While it may seem convenient, it introduces a hidden dependency on the current state of the Docker registry. If a new version is pushed and latest points to it, your deployment may unexpectedly change – even if you made no modifications yourself.

This undermines reproducibility and stability, which are essential properties of event-sourced systems. Since EventSourcingDB guarantees deterministic behavior and strong consistency, upgrading the database should always be a conscious decision, not an accidental side effect of using an unpinned tag.

Always use a specific version tag (e.g. 1.2.0) when pulling or deploying the image. This ensures predictable behavior across environments, simplifies debugging, and avoids surprises during testing or recovery.

Assuming EventSourcingDB Provides Read Models

EventSourcingDB stores events. It does not maintain projections, aggregates, or queryable views. These must be implemented 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

EventSourcingDB provides at-least-once delivery for observers. It does not guarantee exactly once semantics. 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.

Using EventQL for Live Event Processing

EventQL is designed for analyzing and transforming historical data. It is not a substitute for live observation or streaming systems.

Use the observation API when you need to react to events as they happen. Use EventQL for projections, exports, and query-based reporting.

Management UI Not Accessible in Browser

If the management UI is not accessible in your browser but the API works correctly (e.g., curl requests succeed), check if you're using a browser-blocked port.

Browsers block certain ports for security reasons according to the WHATWG Fetch specification. Common examples include port 22 (SSH), port 25 (SMTP), port 6000 (X11), and port 6665 (IRC).

To solve this issue, restart EventSourcingDB using a different port, such as 3000, 4000, 8080, or 8443. For more information on changing ports, see Running EventSourcingDB.