Skip to content

Time is of the Essence

First of all: We wish you a happy new year! We hope you had a wonderful Christmas and an amazing time! 🎉

Speaking of time, let's talk about one of the most commonly misunderstood concepts in event sourcing: when did something actually happen?

If you've worked with events for a while, you've probably noticed that every event comes with a timestamp. In CloudEvents, this is the time field, which EventSourcingDB sets automatically when an event is stored. It's tempting to use this timestamp for business logic. After all, it's right there, readily available. But doing so can lead to subtle bugs and incorrect assumptions. Here's why, and what to do instead.

The Problem: When Was This Book Borrowed?

Let's use our familiar library example. A reader borrows a book, and the system records the following event:

{
  "source": "https://library.eventsourcingdb.io",
  "subject": "/books/42",
  "type": "io.eventsourcingdb.library.book-borrowed",
  "data": {
    "borrowedBy": "/readers/23",
    "borrowedUntil": "2026-02-12"
  }
}

Notice something? The event contains borrowedUntil (the due date) but not borrowedAt. If we want to know when the book was actually borrowed, we'd have to rely on the event's technical timestamp.

But here's the catch: what if the reader checked out the book at 2:00 PM, but the system didn't record the event until 2:02 PM? Or what if the librarian entered yesterday's loans this morning? The technical timestamp tells us when the event was stored, not when the borrowing occurred.

Why Technical Timestamps Are Not Business Timestamps

There are several reasons why relying on the technical timestamp for business logic is problematic.

A Fundamental Distinction

First, a technical timestamp describes when the system did something. A business timestamp describes when something happened in the real world. These are two fundamentally different concepts, and conflating them leads to confusion.

Consider this: the event's time field answers the question "When was this event recorded?" But the business often needs to answer "When did this actually happen?" These are not the same question.

And please note: This is not about precision or latency. It's about semantics.

Format Dependency

Second, the existence of a timestamp field depends entirely on the event format you're using. CloudEvents includes a time field, but other formats might not. If you ever need to convert events to a different format (for archiving, export, or integration with other systems), the technical timestamp might not survive the transformation.

EventSourcingDB guarantees that timestamps are preserved during backup and restore operations. But other systems might not make the same guarantee. If your business logic depends on a field that could disappear during a migration, you're building on unstable ground.

Events Are Often Recorded After the Fact

And third, and perhaps this is the strongest argument: events are frequently recorded after the actual occurrence.

Consider these scenarios:

  • Corrections: A librarian realizes that yesterday's loan was entered with the wrong date. The correction event is recorded today, but it refers to something that happened yesterday.
  • Offline scenarios: A mobile library app records a loan while disconnected. When the device syncs hours later, the event is finally persisted, but the actual borrowing happened much earlier.
  • Batch imports: When migrating from a legacy system, you import years of historical events. The technical timestamps would all show today's date, making historical analysis meaningless.
  • System outages: If the event store was temporarily unavailable, events might be queued and written later. The technical timestamp reflects the write time, not the original occurrence.

In all these cases, using the technical timestamp for business logic would give you wrong answers.

The Solution: Make Time Explicit

If time matters to your business, put it in your event data. Here's the improved version of our book-borrowed event:

{
  "source": "https://library.eventsourcingdb.io",
  "subject": "/books/42",
  "type": "io.eventsourcingdb.library.book-borrowed",
  "data": {
    "borrowedBy": "/readers/23",
    "borrowedAt": "2026-01-12T14:00:00.000Z",
    "borrowedUntil": "2026-02-12"
  }
}

Now the semantics are clear:

  • borrowedAt: When the book was actually borrowed (business time)
  • borrowedUntil: When the book is due (business time, this was already correct!)
  • Event timestamp: When the system recorded this event (technical time)

Notice that borrowedUntil was already a business time field. We just didn't have its counterpart borrowedAt. This inconsistency is common: developers often remember to include future dates (deadlines, due dates, expiration times) but forget to include past dates (when something actually happened).

More Examples

The pattern extends to other events in our library domain:

Event Type Business Time Field Meaning
book-borrowed borrowedAt When the book was borrowed
book-returned returnedAt When the book was returned
reader-applied appliedAt When the reader submitted their application
late-fee-charged chargedFor The period the fee covers

Each of these represents when something happened in the real world, independent of when the system recorded it.

When Is the Technical Timestamp Acceptable?

Now, does this mean you should ignore the technical timestamp entirely? Not at all. There are legitimate uses:

  • Debugging: Understanding the sequence in which events were persisted
  • Monitoring: Measuring system latency and throughput
  • Audit trails: Documenting when records entered the system

But these are all technical concerns, not business concerns.

What about using the technical timestamp as a fallback when you forgot to add a business time field? That's a workaround, not a solution. If you find yourself in this situation, consider introducing a new version of the event type that includes the explicit time field. Yes, you'll have to handle both versions in your projections, but you'll gain clarity and correctness going forward.

And please, don't rationalize it with "the latency is small enough" or "we don't need high precision." These arguments miss the point. As said above, the issue isn't precision, it's semantic clarity. A technical timestamp and a business timestamp answer different questions, regardless of how close their values might be.

Make Time Explicit

Time is one of those things that seems simple until you look closely. In event sourcing, the distinction between "when something happened" and "when the system recorded it" is fundamental. Blurring this line leads to subtle bugs, incorrect reports, and systems that don't accurately reflect reality.

The fix is straightforward: if time matters to your business, make it explicit in your event data. Your future self (and anyone who needs to analyze your events) will thank you.

Here's to a year of well-modeled events. Happy 2026!