Learn / Tutorials / First Steps with EventSourcingDB / Observing Events
Step 4 of 8

Observing Events

This guide shows how to observe events in EventSourcingDB using its HTTP API. You'll learn how to subscribe to live events as well as how to replay historic events if needed.

Observing Events for a Single Subject#

Just like reading events, observing events for a single subject is the simplest possible approach.

Apart from using the /api/v2/data/observe-events route instead of /api/v2/data/read-events, the request is conceptually the same, including the method QUERY:

curl \
  -i \
  -X QUERY \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\"
  }" \
  http://localhost:3000/api/v2/data/observe-events

If everything worked as expected, the server will respond with HTTP status code 200 OK:

HTTP/1.1 200 OK

As with reading, the response body initially contains all previously stored events in NDJSON format – but the difference is what happens next: While reading ends with the end item after all events have been returned, observing keeps the connection open and never sends an end item.

To prevent intermediate proxies or other network components from closing the connection due to inactivity, a heartbeat item is sent whenever the stream has sent nothing for a second:

{"type":"heartbeat","data":{}}

Whenever a new event is written via another connection, it will be streamed through this route as well.

This allows clients to subscribe to a subject and get notified about new events as soon as they happen. An observation delivers every event exactly once: the events that already existed when it started and those written while it runs follow on from each other, with no gap and no duplicate between them.

Observing Events for Multiple or All Subjects#

To observe events for multiple subjects at once, you can – just like when reading – choose an appropriate base subject and set the recursive parameter to true.

The following request shows how to observe all events stored in the database. In addition to the recursive flag, it is important to specify the root subject /:

curl \
  -i \
  -X QUERY \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/\",
    \"options\": {
      \"recursive\": true
    }
  }" \
  http://localhost:3000/api/v2/data/observe-events

When an Observation Ends#

Since an observation has no end item, it runs until the connection closes. EventSourcingDB can also end it itself, with an error item as its last line. Two cases are worth knowing:

  • When the server shuts down, the observation ends with server-shutting-down. Observe again once the server is back, resuming as described below.
  • When a restore begins, the observation ends with restore-in-progress, since the restore replaces the events that the observation is based on. Observe again once the restore has finished.

For every error and how it arrives, see the overview of the errors.

An observation also ends if your client stops taking in what the server sends: the server waits up to 60 seconds for the client to take in a line, and then cuts the connection. So read the stream continuously, and don't let slow processing of the events keep your client from reading it.

Resuming After Connection Loss#

If a client gets disconnected from the database, it can reconnect at any time. It will first receive all existing events again, followed by live updates.

If the client remembers the ID of the last event it received, it can resume from that exact point by setting the lowerBound parameter. For example, if the last received event had ID 2010, the client can use the following request to proceed from the event following the given one:

curl \
  -i \
  -X QUERY \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/\",
    \"options\": {
      \"recursive\": true,
      \"lowerBound\": {
        \"id\": \"2010\",
        \"type\": \"exclusive\"
      }
    }
  }" \
  http://localhost:3000/api/v2/data/observe-events

This ensures that no events are lost even if the connection is temporarily unavailable.

Observing From the Last Event of a Given Type#

Also similar to reading, you can start observing from the last occurrence of a specific event type using the fromLatestEvent parameter. For example, to observe all events that happen after a book was last borrowed, use this command:

curl \
  -i \
  -X QUERY \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\",
    \"options\": {
      \"recursive\": true,
      \"fromLatestEvent\": {
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-borrowed\",
        \"ifEventIsMissing\": \"wait-for-event\"
      }
    }
  }" \
  http://localhost:3000/api/v2/data/observe-events

The ifEventIsMissing parameter controls what should happen if no matching event is found:

  • If set to read-everything, all events for the given subject will be read.
  • If set to wait-for-event, the request will wait until a matching event occurs, and then begin observing.

This also means that if you specify a non-existent subject, you won't get an error. Instead, the behavior depends on the value of the ifEventIsMissing parameter.

Incompatible Parameters

You cannot combine fromLatestEvent with lowerBound. EventSourcingDB refuses the combination with selection-conflicting.

Your Turn#

Now that you know how to write, read, and observe events, try combining them – write events and attach one or more observers at the same time.

Here are some ideas:

  • Observe all events for a book that does not exist yet, then create the book.
  • Observe all events for all readers.
  • Observe all events across all books and readers, disconnect in between, then reconnect using the ID of the last received event to resume.