Getting Started with EventStoreDB: A Practical Guide to Event Sourcing

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Why Move Beyond CRUD?

Traditional databases like PostgreSQL or MySQL excel at storing the current state of an object. However, they are notoriously bad at explaining why that state changed. If a customer updates their shipping address, a standard UPDATE statement destroys the old data forever. You lose the history unless you build complex, high-maintenance audit tables. EventStoreDB approaches data differently by treating every change as an immutable event.

Imagine a bank ledger. A bank doesn’t just store your final balance of $5,000. It records every $100 deposit and $50 withdrawal.

Your current balance is simply the sum of those historical facts. This is Event Sourcing. EventStoreDB is a specialized database engine built to handle this at scale, capable of sustaining over 15,000 writes per second on modest hardware. It provides native support for CQRS (Command Query Responsibility Segregation), which is essential for scaling complex microservices.

Choosing a dedicated tool like EventStoreDB beats hacking an RDBMS to act as an event store. It manages stream atomicity and optimistic concurrency natively. This architectural choice eliminates the need for complex locking logic in your application code.

Installation: Deploying Your First Node

Docker is the most efficient way to spin up a local instance. It provides a sandboxed environment that requires less than 2GB of RAM for development, making it perfect for testing on a laptop.

Running with Docker

Execute this command to pull the latest 24.10 LTS image and start a single-node cluster. We use the --insecure flag here to skip SSL certificate configuration for local testing.

docker run --name esdb-node -it \
  -p 2113:2113 -p 1113:1113 \
  eventstore/eventstore:latest --insecure --run-projections=all

Let’s break down these parameters:

  • -p 2113:2113: Maps the port for the Admin UI and the gRPC API. Modern EventStoreDB versions use HTTP/2 for all data operations.
  • –insecure: Disables TLS. This is a massive time-saver for local dev but is a major security risk in production environments.
  • –run-projections=all: Activates the internal engine that aggregates and transforms event data.

Native Linux Installation

For those running Debian or Ubuntu who prefer a native service, you can pull the official package directly from PackageCloud:

curl -s https://packagecloud.io/install/repositories/EventStore/EventStore-OSS/script.deb.sh | sudo bash
sudo apt-get install eventstore-oss

Once the installation finishes, fire up the service with systemd:

sudo systemctl start eventstore

Essential Network Configuration

EventStoreDB looks at the eventstore.conf file for its marching orders. By default, it restricts traffic to 127.0.0.1. If you are deploying this to a cloud VM and need remote access, you must adjust the binding interfaces.

Update your configuration file with these settings:

# Path: /etc/eventstore/eventstore.conf

IntIp: 0.0.0.0
ExtIp: 0.0.0.0
HttpPort: 2113
Insecure: true
RunProjections: All

During a recent logistics project, I spent two hours debugging a connection only to realize the firewall was blocking gRPC traffic. Ensure your security groups allow ingress on port 2113. This port handles both the browser-based dashboard and the high-speed data stream.

Writing Your First Event with Python

Interacting with the database is straightforward thanks to modern SDKs. While .NET is the first-class citizen here, Python is fantastic for rapid prototyping. Start by installing the client:

pip install esdbclient

The following script creates a stream for a specific user and appends a signup event. This creates a permanent, unchangeable record of the user’s entry into your system.

from esdbclient import EventStoreDBClient, NewEvent
import json

# Connect to the local instance
client = EventStoreDBClient(uri="esdb://localhost:2113?tls=false")

# Define the payload
event_payload = {
    "user_id": "u-789",
    "action": "account_created",
    "email": "[email protected]"
}

# Package the event
event = NewEvent(
    type="UserCreated",
    data=json.dumps(event_payload).encode("utf-8")
)

# Append to the 'user-789' stream
client.append_to_stream(
    stream_name="user-789",
    current_version=-1, # Ensures the stream is brand new
    events=[event]
)

print("Event committed to the ledger.")

If you are migrating legacy data from spreadsheets, formatting can be a headache. I use toolcraft.app/en/tools/data/csv-to-json to convert rows into clean JSON objects. It runs entirely in your browser. This ensures sensitive customer data never leaves your local machine during the conversion process.

Validating the Data Stream

Verification is simple because the database includes a robust web interface. You don’t need a separate CLI tool to see what’s happening under the hood.

  1. Navigate to http://localhost:2113 in your browser.
  2. Sign in using admin and the default password changeit.
  3. Select the “Stream Browser” from the sidebar.

You will see the user-789 stream listed immediately. Clicking it reveals the individual events, complete with timestamps and metadata. This transparency is a lifesaver when debugging event-driven race conditions.

Health and Performance Monitoring

Production environments require more than just a UI. EventStoreDB provides a /stats endpoint that outputs metrics in JSON. Most teams pipe this into Prometheus. You can find pre-built Grafana dashboards that track disk I/O, memory pressure, and event throughput.

Keep a close eye on the “Scavenging” process in the Admin UI. Scavenging is the database’s way of cleaning up deleted events or expired stream versions to reclaim disk space. If your storage usage spikes, check the scavenge logs first before adding more disk capacity.

Deriving State with Projections

Projections are the engine’s secret weapon. They are JavaScript snippets that run server-side to react to events as they arrive. Instead of querying every user event to find a total count, you can write a projection that maintains a real-time counter.

Enable the $by_category system projection to automatically organize your data. It groups events from user-1, user-2, and user-3 into a single virtual stream called $ce-user. Subscribing to this single stream allows your downstream services to react to every user-related change across the entire platform without manual filtering.

Summary

EventStoreDB requires a mental shift from snapshots to timelines. By treating data as a sequence of events, you gain a perfect audit log and the ability to rebuild your system state at any point in time. Start small by modeling a simple workflow—like a shopping cart—and observe how the streams grow. Once you experience the reliability of an append-only architecture, traditional CRUD feels like working with a blindfold on.

Share: