Bắt đầu với EventStoreDB: Hướng dẫn thực hành về Event Sourcing

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

Tại sao nên vượt xa khỏi CRUD?

Các cơ sở dữ liệu truyền thống như PostgreSQL hay MySQL rất giỏi trong việc lưu trữ trạng thái hiện tại của một đối tượng. Tuy nhiên, chúng lại nổi tiếng là kém trong việc giải thích tại sao trạng thái đó lại thay đổi. Nếu một khách hàng cập nhật địa chỉ giao hàng, một câu lệnh UPDATE thông thường sẽ xóa bỏ dữ liệu cũ mãi mãi. Bạn sẽ mất đi lịch sử trừ khi bạn xây dựng các bảng audit phức tạp và khó bảo trì. EventStoreDB tiếp cận dữ liệu theo cách khác bằng cách coi mọi thay đổi là một sự kiện bất biến (immutable event).

Hãy tưởng tượng một sổ cái ngân hàng. Ngân hàng không chỉ lưu trữ số dư cuối cùng của bạn là 5.000 USD. Nó ghi lại mọi khoản tiền gửi 100 USD và mọi khoản rút tiền 50 USD.

Số dư hiện tại của bạn đơn giản là tổng của các sự kiện lịch sử đó. Đây chính là Event Sourcing. EventStoreDB là một engine cơ sở dữ liệu chuyên dụng được xây dựng để xử lý việc này ở quy mô lớn, có khả năng duy trì hơn 15.000 lượt ghi mỗi giây trên phần cứng khiêm tốn. Nó cung cấp hỗ trợ gốc cho CQRS (Command Query Responsibility Segregation), một yếu tố thiết yếu để mở rộng các microservices phức tạp.

Việc chọn một công cụ chuyên dụng như EventStoreDB tốt hơn nhiều so với việc cố gắng tùy biến một RDBMS để hoạt động như một event store. Nó quản lý tính nguyên tử của stream (stream atomicity) và quản lý truy cập đồng thời lạc quan (optimistic concurrency) một cách tự nhiên. Lựa chọn kiến trúc này giúp loại bỏ nhu cầu về các logic khóa (locking logic) phức tạp trong mã nguồn ứng dụng của bạn.

Cài đặt: Triển khai Node đầu tiên của bạn

Docker là cách hiệu quả nhất để khởi chạy một instance cục bộ. Nó cung cấp một môi trường sandbox chỉ yêu cầu chưa đến 2GB RAM để phát triển, hoàn hảo để thử nghiệm trên máy tính xách tay.

Chạy với Docker

Thực thi lệnh này để tải image 24.10 LTS mới nhất và bắt đầu một cluster node đơn. Chúng ta sử dụng flag --insecure ở đây để bỏ qua cấu hình chứng chỉ SSL khi thử nghiệm cục bộ.

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

Hãy cùng phân tích các tham số này:

  • -p 2113:2113: Ánh xạ cổng cho Admin UI và gRPC API. Các phiên bản EventStoreDB hiện đại sử dụng HTTP/2 cho mọi hoạt động dữ liệu.
  • –insecure: Vô hiệu hóa TLS. Điều này giúp tiết kiệm thời gian đáng kể khi phát triển cục bộ nhưng là một rủi ro bảo mật lớn trong môi trường production.
  • –run-projections=all: Kích hoạt engine nội bộ chuyên dùng để tổng hợp và chuyển đổi dữ liệu sự kiện.

Cài đặt trực tiếp trên Linux

Đối với những người dùng Debian hoặc Ubuntu muốn chạy như một dịch vụ hệ thống, bạn có thể tải gói chính thức trực tiếp từ PackageCloud:

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

Sau khi cài đặt xong, hãy khởi động dịch vụ bằng systemd:

sudo systemctl start eventstore

Cấu hình mạng thiết yếu

EventStoreDB dựa vào tệp eventstore.conf để nhận lệnh điều hành. Theo mặc định, nó hạn chế lưu lượng truy cập ở 127.0.0.1. Nếu bạn đang triển khai trên một VM đám mây và cần truy cập từ xa, bạn phải điều chỉnh các giao diện ràng buộc (binding interfaces).

Cập nhật tệp cấu hình của bạn với các cài đặt sau:

# Đường dẫn: /etc/eventstore/eventstore.conf

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

Trong một dự án logistics gần đây, tôi đã mất hai giờ để debug một kết nối chỉ để nhận ra rằng tường lửa đang chặn lưu lượng gRPC. Hãy đảm bảo các security group của bạn cho phép truy cập ở cổng 2113. Cổng này xử lý cả bảng điều khiển trên trình duyệt và luồng dữ liệu tốc độ cao.

Viết sự kiện đầu tiên bằng Python

Tương tác với cơ sở dữ liệu rất đơn giản nhờ các SDK hiện đại. Mặc dù .NET là ngôn ngữ được ưu tiên hàng đầu ở đây, nhưng Python cũng rất tuyệt vời để tạo prototype nhanh chóng. Bắt đầu bằng cách cài đặt client:

pip install esdbclient

Script sau đây tạo một stream cho một người dùng cụ thể và thêm vào một sự kiện đăng ký. Điều này tạo ra một bản ghi vĩnh viễn, không thể thay đổi về việc người dùng gia nhập hệ thống của bạn.

from esdbclient import EventStoreDBClient, NewEvent
import json

# Kết nối tới instance cục bộ
client = EventStoreDBClient(uri="esdb://localhost:2113?tls=false")

# Định nghĩa payload dữ liệu
event_payload = {
    "user_id": "u-789",
    "action": "tài_khoản_đã_tạo",
    "email": "[email protected]"
}

# Đóng gói sự kiện
event = NewEvent(
    type="UserCreated",
    data=json.dumps(event_payload).encode("utf-8")
)

# Thêm vào stream 'user-789'
client.append_to_stream(
    stream_name="user-789",
    current_version=-1, # Đảm bảo stream này là hoàn toàn mới
    events=[event]
)

print("Sự kiện đã được ghi vào sổ cái.")

Nếu bạn đang chuyển đổi dữ liệu cũ từ bảng tính, việc định dạng có thể gây đau đầu. Tôi thường sử dụng toolcraft.app/vi/tools/data/csv-to-json để chuyển đổi các hàng thành các đối tượng JSON sạch sẽ. Nó chạy hoàn toàn trong trình duyệt của bạn, đảm bảo dữ liệu nhạy cảm của khách hàng không bao giờ rời khỏi máy cục bộ trong quá trình chuyển đổi.

Xác thực luồng dữ liệu (Data Stream)

Việc xác thực rất đơn giản vì cơ sở dữ liệu tích hợp sẵn một giao diện web mạnh mẽ. Bạn không cần một công cụ CLI riêng biệt để xem những gì đang diễn ra bên dưới hệ thống.

  1. Truy cập http://localhost:2113 trong trình duyệt của bạn.
  2. Đăng nhập bằng tài khoản admin và mật khẩu mặc định là changeit.
  3. Chọn “Stream Browser” từ thanh bên.

Bạn sẽ thấy stream user-789 được liệt kê ngay lập tức. Nhấp vào đó sẽ hiển thị các sự kiện riêng lẻ, đi kèm với dấu thời gian (timestamp) và siêu dữ liệu (metadata). Sự minh bạch này là cứu cánh khi debug các lỗi race condition trong kiến trúc hướng sự kiện.

Giám sát sức khỏe và hiệu suất

Môi trường production yêu cầu nhiều hơn là chỉ một giao diện UI. EventStoreDB cung cấp một endpoint /stats xuất ra các chỉ số ở định dạng JSON. Hầu hết các đội ngũ kỹ thuật sẽ chuyển dữ liệu này vào Prometheus. Bạn có thể tìm thấy các dashboard Grafana được xây dựng sẵn để theo dõi I/O đĩa, áp lực bộ nhớ và thông lượng sự kiện.

Hãy để mắt kỹ đến quá trình “Scavenging” trong Admin UI. Scavenging là cách cơ sở dữ liệu dọn dẹp các sự kiện đã xóa hoặc các phiên bản stream đã hết hạn để thu hồi không gian đĩa. Nếu dung lượng lưu trữ của bạn tăng đột biến, hãy kiểm tra nhật ký scavenge trước khi quyết định tăng thêm dung lượng đĩa.

Suy diễn trạng thái với Projections

Projections là vũ khí bí mật của engine này. Chúng là các đoạn mã JavaScript chạy ở phía server để phản ứng với các sự kiện khi chúng đến. Thay vì truy vấn mọi sự kiện của người dùng để tìm tổng số lượng, bạn có thể viết một projection để duy trì một bộ đếm thời gian thực.

Kích hoạt hệ thống projection $by_category để tự động sắp xếp dữ liệu của bạn. Nó nhóm các sự kiện từ user-1, user-2, và user-3 vào một stream ảo duy nhất gọi là $ce-user. Việc đăng ký (subscribing) vào stream duy nhất này cho phép các dịch vụ hạ nguồn phản ứng với mọi thay đổi liên quan đến người dùng trên toàn bộ nền tảng mà không cần lọc thủ công.

Tổng kết

EventStoreDB đòi hỏi một sự thay đổi tư duy từ việc lưu trữ ảnh chụp trạng thái (snapshots) sang dòng thời gian (timelines). Bằng cách coi dữ liệu là một chuỗi các sự kiện, bạn có được một nhật ký audit hoàn hảo và khả năng xây dựng lại trạng thái hệ thống tại bất kỳ thời điểm nào. Hãy bắt đầu từ những việc nhỏ bằng cách mô hình hóa một quy trình đơn giản — như giỏ hàng — và quan sát cách các stream phát triển. Một khi bạn trải nghiệm độ tin cậy của kiến trúc append-only, bạn sẽ thấy CRUD truyền thống giống như đang làm việc trong tình trạng bị bịt mắt vậy.

Share: