Event sourcing asks you to stop overwriting your data. In a traditional CRUD application, updating a user’s shipping address means finding the row, changing the value, and discarding the previous state. Event sourcing takes a different path. It stores each change as an immutable fact: a user created an account, updated their address, verified their email. The current state of the system is not stored directly. It is computed by replaying these events in order.
This pattern solves real problems. Audit trails become free byproducts. You can reconstruct the state of an order at any past moment. You can debug by replaying exactly what happened. The trade-off is complexity. You now manage streams of facts, read models, and eventual consistency instead of simple rows.
PostgreSQL can act as your event store. Most teams already run it. It offers ACID transactions, JSONB for flexible payloads, and proven backup tools. You do not need to introduce Kafka, Cassandra, or a specialized event-store database on day one. A standard Postgres instance gives you the transactional guarantees and audit trails that event sourcing demands without expanding your infrastructure footprint.
The Shape of a Postgres Event Store
The schema can be almost embarrassingly simple. At minimum, you need a table that appends events and never updates them in place. A practical design looks like this:
idas a bigserial or UUID, serving as the global ordering.stream_idto group related events, such as all changes for a single user or order.event_typeas plain text:UserEmailChanged,PaymentReceived,InventoryAdjusted.payloadas JSONB, holding the specific data for that occurrence.occurred_atwith timezone precision.versionper stream, enforcing optimistic concurrency.
You enforce the immutability rule in application code or with a database constraint. A unique index on (stream_id, version) prevents two writers from appending the same sequence number. When a command comes in, you read the current version for that stream, increment it, and insert the new event inside a transaction. If another process beat you to it, the unique constraint fails, and you retry or reject the command.
Consider a concrete example. You run an inventory system. Instead of a single inventory row with a quantity column, you append events to a inventory_events table. ItemReceived adds ten units. ItemReserved removes two. ItemShipped removes three. To know the current stock for SKU-42, you sum the relevant event payloads. To know the stock three days ago, you sum only up to that timestamp. If a bug in your shipping logic caused an error last Tuesday, you replay the events through corrected code to get the true state. You cannot do that with a simple UPDATE statement.
Principles That Keep You Out of Trouble
Building on Postgres does not remove the need for discipline. The following principles apply directly to event-sourced systems.
Keep it simple. Complexity kills reliability. Resist the urge to build a generic event framework before you have shipped one working flow. A single table, a repository function to append events, and a projection worker to build read models are enough to prove value. Add tools only when a concrete problem appears.
Start small. Do not rewrite your entire monolith. Pick one bounded context where the audit trail pays for the overhead. A billing ledger, a workflow engine, or an inventory reservation system are good candidates. Build that one pipeline end-to-end. Let it run in production. Then decide whether to expand.
Define success first. Event sourcing is not a default architecture; it is a solution to specific needs. If your requirement is only to track the latest state, CRUD is faster and cheaper. If you need temporal queries, strict auditability, or the ability to rebuild read models on demand, then events make sense. Know which problem you are solving before you commit.
Measure before you optimize. Modern PostgreSQL on modest hardware can ingest thousands of events per second with a simple append-only table. Do not shard your event store or introduce complex partitioning schemes until your monitoring proves you have exhausted simpler fixes. Index the fields you query. Tune autovacuum for append-only workloads. Then measure again.
Kiểm thử mọi thứ. Hãy viết unit test cho các trình xử lý sự kiện (event handlers) của bạn. Kiểm thử tích hợp (integration test) luồng ghi thêm (append path). Quan trọng nhất là kiểm thử các kịch bản lỗi. Điều gì xảy ra khi hai nút (node) cùng ghi thêm vào một stream đồng thời? Điều gì xảy ra khi một worker xử lý projection bị sập giữa chừng trong một batch? Hãy viết các bài kiểm thử để xác minh tính đồng thời lạc quan (optimistic concurrency) và các cam kết phân phối ít nhất một lần (at-least-once delivery) của bạn.
Giám sát trong môi trường production. Bảng sự kiện (events table) sẽ ngày càng lớn dần. Khác với lược đồ chuẩn hóa (normalized schema) nơi các bản cập nhật giữ cho số lượng hàng ổn định, event sourcing được thiết kế có chủ đích theo hướng cộng dồn (additive). Hãy theo dõi kích thước bảng, I/O đĩa và độ trễ (lag) giữa write model và các projection của read model. Thiết lập cảnh báo về độ trễ projection trước khi người dùng nhận thấy dữ liệu bị cũ (stale data).
Tự động hóa các tác vụ thủ công. Thay đổi schema thủ công, xây dựng lại projection thủ công và phát lại sự kiện (event replay) thủ công là những quả bom hẹn giờ. Hãy viết script cho chiến lược di chuyển (migration) của bạn. Nếu bạn phát triển một event schema, hãy tự động hóa việc upcasting hoặc chuyển đổi để các sự kiện cũ có thể được phát lại thông qua logic mới mà không cần sự can thiệp của con người vào lúc nửa đêm.
Tài liệu hóa các lựa chọn của bạn. Hãy ghi lại lý do tại sao các stream cụ thể tồn tại, ý nghĩa của từng loại sự kiện và khi nào nhóm nên chọn CRUD thay vì sự kiện. Event sourcing làm tăng tải nhận thức (cognitive load). Tài liệu tốt sẽ ngăn chặn việc một kỹ sư mới đoán sai và ghi thêm các sự kiện sai định dạng vào một stream quan trọng.
Những cạm bẫy gây lãng phí hàng tháng trời
Event sourcing nghe có vẻ thanh thoát trên sơ đồ nhưng lại đầy đau đớn trong môi trường production. Hãy cảnh giác với những cạm bẫy sau.
Đánh giá thấp độ phức tạp. Việc phát lại các sự kiện để xây dựng lại trạng thái về mặt khái niệm là đơn giản. Tuy nhiên, việc quản lý tính lũy đẳng (idempotency), chụp ảnh nhanh (snapshotting) để tăng hiệu suất và các giao dịch bù đắp (compensating transactions) giữa các aggregate thì không hề đơn giản. Hãy chia nhỏ hệ thống của bạn. Giải quyết từng stream một.
Thiết kế quá mức (Over-engineering). Đừng triển khai một cụm Kafka đa nút (multi-node) chỉ vì bạn tưởng tượng rằng khối lượng sự kiện của mình một ngày nào đó sẽ cần đến nó. Postgres có thể hỗ trợ bạn đi xa một cách đáng ngạc nhiên. Chỉ đưa vào cơ sở hạ tầng mới khi bạn đã đo lường được điểm nghẽn mà bạn không thể khắc phục trong thiết lập hiện tại.
Phớt lờ nợ kỹ thuật. Các event schema cũ sẽ tồn tại mãi mãi. Nếu bạn thay đổi payload của OrderCreated, bạn vẫn còn mười triệu sự kiện lịch sử theo định dạng cũ. Hãy theo dõi khoản nợ này. Lập kế hoạch cho các trình đọc tương thích ngược hoặc các script di chuyển. Đừng để gánh nặng của các sự kiện cũ làm chậm mọi tính năng mới.
Chọn những công cụ mà nhóm không thể vận hành. Kiến trúc tốt nhất cũng sẽ thất bại nếu chỉ có một người hiểu nó. Nếu nhóm của bạn biết Postgres và SQL, hãy bắt đầu từ đó. Nếu bạn đưa vào một event store chuyên dụng, hãy đảm bảo rằng bạn có chuyên môn vận hành để debug nó vào lúc hai giờ sáng.
Một điểm bắt đầu thực tế
Nếu cách tiếp cận này phù hợp với vấn đề của bạn, đừng đợi đến khi phải viết lại toàn bộ trong năm quý tới. Hãy bắt đầu ngay tuần này.
Hãy kiểm tra các hệ thống hiện tại của bạn. Tìm một nơi mà nhật ký kiểm tra (audit trail) có thể giải quyết nỗi đau thực sự. Có thể đó là một máy trạng thái đơn hàng (order state machine) hiện chỉ duy trì một cột status duy nhất. Có lẽ đó là một sổ cái tài chính nơi việc điều chỉnh số dư yêu cầu các bản vá cơ sở dữ liệu thủ công. Hãy chọn một lỗ hổng mà việc ghi đè trạng thái đã gây khó khăn cho bạn.
Sau đó, hãy chọn một cải tiến nhỏ mà bạn có thể thực hiện ngay hôm nay. Tạo một bảng sự kiện. Mô hình hóa một stream. Viết một projection để xây dựng read model từ các sự kiện đó. Triển khai nó dưới một feature flag. Quan sát nó xử lý lưu lượng truy cập thực tế.
Event sourcing với PostgreSQL không phải là phép màu. Nó là một công cụ thực tế cho các nhóm cần biết không chỉ mọi thứ đang ở đâu, mà còn cả cách chúng đã đến đó như thế nào. Hãy xây dựng chậm rãi, đo lường trung thực và để các yêu cầu thực tế dẫn dắt kiến trúc của bạn.
