
Event Sourcing là một kiến trúc phần mềm lưu toàn bộ thay đổi trạng thái ứng dụng dưới dạng một chuỗi sự kiện bất biến, thay vì chỉ giữ lại trạng thái hiện tại trong cơ sở dữ liệu. Thay vì ghi đè dòng dữ liệu, hệ thống ghi thêm một sự kiện mới. Khi cần biết ứng dụng đang ở đâu, ta chỉ việc phát lại chuỗi sự kiện đó. Bài viết này giải thích nguyên lý, kiến trúc, lợi ích, đánh đổi và cách áp dụng thực tế.
Cách truyền thống mà hầu hết hệ thống dùng là CRUD: bạn đọc trạng thái hiện tại, thay đổi nó, rồi lưu đè. Cách này hiệu quả cho phần lớn ứng dụng, nhưng nó có một điểm mù: bạn biết trạng thái bây giờ mà không biết nó đến từ đâu. Event Sourcing giải quyết đúng điểm mù đó bằng cách làm nguồn sự thật trở thành dòng thời gian các sự kiện, không phải ảnh chụp trạng thái tại một thời điểm.

Event Sourcing khác CRUD ở chỗ nào
Giả sử bạn xây dựng hệ thống theo dõi tàu biển. Với kiến trúc CRUD, bảng ships của bạn chỉ lưu vị trí hiện tại. Khi tàu King Roy rời San Francisco và đến Hồng Kông, hai lần cập nhật sẽ ghi đè lên nhau, và bạn chỉ còn lại một dòng dữ liệu. Không ai biết tàu từng đi qua đâu, dừng ở cảng nào, hay ai là người đã ra lệnh.
Event Sourcing thêm một tầng gián tiếp: thay vì sửa trực tiếp đối tượng tàu, dịch vụ tạo ra một đối tượng sự kiện ghi nhận thay đổi, rồi áp dụng sự kiện đó lên đối tượng tàu. Bản thân sự kiện được lưu xuống, giữ nguyên thứ tự xảy ra, và sống lâu bằng chính ứng dụng.
Martin Fowler, người viết bài Event Sourcing trong Enterprise Application Architecture, mô tả điểm khác biệt cốt lõi: hệ thống giờ lưu hai thứ khác nhau — một là event log (nhật ký sự kiện) và một là application state (trạng thái hiện tại, được dựng lại từ nhật ký).

Ba thành phần kiến trúc cốt lõi
Một hệ thống Event Sourcing hoàn chỉnh gồm ba phần chính:
- Event store — nơi lưu trữ chuỗi sự kiện theo thứ tự. Đây là nguồn sự thật duy nhất. Thường dùng bảng append-only trong PostgreSQL, hoặc chuyên dụng như EventStoreDB.
- Event handler — đọc sự kiện mới, áp dụng thay đổi lên aggregate hiện tại để tái tạo trạng thái.
- Projection — bản sao trạng thái đã được tính sẵn, tối ưu cho truy vấn đọc, để không phải phát lại toàn bộ nhật ký mỗi lần đọc.
Projection là chi tiết thường bị bỏ qua nhưng lại quyết định hiệu năng. Một hệ thống có thể lưu 10 triệu sự kiện nhưng vẫn phục vụ truy vấn trong mili giây, vì bảng projection được cập nhật liên tục và có index đầy đủ. Người dùng không bao giờ phải chờ hệ thống phát lại toàn bộ lịch sử.
Công thức toán học của Event Sourcing
Trạng thái hiện tại không phải thứ được lưu trực tiếp, mà là kết quả của phép rút gọn hàm sau đây:
State(t) = Reduce(Events[0..t])
Trong đó Reduce là hàm thuần túy (pure function) — cùng đầu vào luôn cho cùng đầu ra, không đụng tới cơ sở dữ liệu bên ngoài, không phát sinh side effect. Chính tính chất này cho phép bạn tính lại trạng thái quá khứ bất cứ lúc nào, hoặc chạy song song nhiều phiên bản xử lý khác nhau trên cùng một chuỗi sự kiện để đối chiếu kết quả.
| Khía cạnh | CRD truyền thống | Event Sourcing |
|---|---|---|
| Nguồn sự thật | Trạng thái hiện tại trong bảng | Chuỗi sự kiện bất biến |
| Thao tác ghi | Ghi đè (UPDATE) | Chỉ thêm (INSERT) |
| Truy vấn quá khứ | Không thể, dữ liệu đã mất | Phát lại từ event log |
| Điều tra lỗi | Khó, thiếu ngữ cảnh | Dễ, có đầy đủ dấu vết |
| Thay đổi nghiệp vụ về sau | Phải viết migration dữ liệu | Chạy lại handler với logic mới |
| Độ phức tạp ban đầu | Thấp | Cao hơn đáng kể |
Điểm mạnh nhất nằm ở hai dòng cuối. Khi quy tắc nghiệp vụ thay đổi — ví dụ cách tính phí vận chuyển — kiến trúc CRUD buộc bạn viết migration script sửa dữ liệu cũ. Với Event Sourcing, bạn chỉ sửa logic trong handler rồi chạy lại projection; dữ liệu gốc vẫn nguyên vẹn.
Lợi ích thực tế
1. Lịch sử trọn vẹn, không thể bị sửa
Event là bất biến theo thiết kế: không có API nào cho phép cập nhật hay xoá một sự kiện đã ghi. Với hệ thống tài chính, y tế hay thương mại điện tử, đây là thuộc tính có giá trị pháp lý và giá trị kiểm toán thực tế.
2. Khôi phục thời gian
Muốn xem trạng thái hệ thống vào lúc 14h30 chiều hôm qua? Phát lại event log đến đúng mốc thời gian đó là xong. Tính năng này gần như không thể xây dựng trên kiến trúc CRUD.
3. Gỡ lỗi và audit nhanh
Khi người dùng báo đơn hàng bị tính tiền sai, kỹ sư có ngay chuỗi sự kiện đầy đủ dẫn tới sai sót đó, thay vì phải đoán từ trạng thái hiện tại. Trong các hệ thống tài chính, khả năng truy vết này gần như bắt buộc.
4. Đồng bộ và tích hợp tự nhiên
Event store trở thành một luồng dữ liệu mà các hệ thống khác có thể đăng ký theo dõi. Đây chính là nền tảng của kiến trúc microservice và event-driven architecture: service này phát sự kiện, service kia phản ứng, không cần gọi trực tiếp qua lại.

Đánh đổi cần cân nhắc
Event Sourcing không phải lựa chọn mặc định đúng cho mọi hệ thống. Nó đi kèm những chi phí thật sự:
- Eventual consistency. Projection luôn có độ trễ so với event log. Người dùng vừa đặt hàng xong có thể chưa thấy trong danh sách. Bạn phải thiết kế UI chịu được điều này, hoặc dùng mẫu command-query responsibility segregation để tách đường ghi và đường đọc.
- Sự bùng nổ của version schema. Sự kiện cũ không được sửa. Khi mô hình nghiệp vụ đổi, bạn phải giữ cả định nghĩa cũ lẫn mới, cùng logic chuyển đổi. Sau vài năm, event store sẽ chứa nhiều phiên bản schema cùng tồn tại.
- Quyền riêng tư. Vì event log vĩnh viễn, dữ liệu đã xoá ở nơi khác vẫn còn trong lịch sử. Ở EU, quy định về quyền bị quên có thể phát sinh vấn đề nghiêm trọng với mô hình này.
- Không phù hợp với dữ liệu không có tính lịch sử. Một bảng tra cứu tỷ giá hay danh mục sản phẩm đơn giản không cần cơ chế này. Áp dụng Event Sourcing ở đó chỉ tăng chi phí vô ích.
- Xử lý sự kiện trùng lặp. Khi hệ thống phân tán, cùng một sự kiện có thể được gửi nhiều lần. Handler của bạn bắt buộc phải idempotent, nghĩa là xử lý lại cùng một sự kiện phải cho kết quả giống hệt lần đầu.
Khi nào nên chọn
Event Sourcing hợp lý khi hệ thống của bạn thỏa mãn phần lớn các điều kiện sau: nghiệp vụ có tính lịch sử rõ ràng (tài chính, giao dịch, vận chuyển), bạn cần truy vết đầy đủ để đáp ứng quy định hoặc hỗ trợ khách hàng, có nhiều hệ thống khác phản ứng với cùng một thay đổi, và đội ngũ kỹ thuật đủ trưởng thành để xử lý sự phức tạp thêm.
Ngược lại, với ứng dụng CRUD thông thường — quản lý kho, hệ thống kế toán đơn giản, blog, trang thương mại điện tử — CRUD vẫn là lựa chọn đúng. Thêm Event Sourcing vào đó là tự tạo gánh nặng mà không đổi lấy lợi ích nào đáng kể.
Áp dụng dần bằng cách kết hợp
Bước đi thực tế phổ biến nhất là mô hình hybrid: hệ thống mới viết sự kiện vào event store đồng thời cập nhật bảng truy vấn phụ thuộc (read model). Bạn không cần viết lại toàn bộ, mà vẫn tích lũy được lịch sử.
Con đường phổ biến khác là áp dụng Event Sourcing cho đúng một phần có tính chất sổ sách — ví dụ module sổ cái hoặc module quản lý tín dụng — rồi để phần còn lại dùng CRUD bình thường. Ranh giới này thường nằm ở chỗ nghiệp vụ có yêu cầu kiểm toán cao nhất.
Lưu ý kỹ thuật quan trọng: tính đồng nhất sự kiện (event immutability) chỉ đảm bảo được nếu dùng cơ sở dữ liệu có ràng buộc phù hợp. Với PostgreSQL, bạn có thể cấp quyền riêng: người dùng ứng dụng chỉ được INSERT, không được UPDATE hay DELETE trên bảng event. Đó là cách biến một quy ước kỹ thuật thành bảo đảm thực sự.
Kết luận
Event Sourcing đánh đổi sự đơn giản ban đầu lấy khả năng truy vết, khôi phục và cải tiến nghiệp vụ về sau — những thứ gần như không thể có trong kiến trúc chỉ lưu trạng thái. Nó không phải câu trả lời cho mọi bài toán, và việc chọn nó một cách mù quáng sẽ gây tổn hại nhiều hơn lợi ích. Nhưng với những hệ thống mà lịch sử là tài sản quan trọng nhất, đây là kiến trúc duy nhất thực sự bảo vệ được dữ liệu đó.
