Nguyen Le PhongNguyen Le Phong

Event Sourcing cho người mới: lưu cả chuyện đã xảy ra

Event Sourcing giữ chuỗi event chỉ nối thêm làm bản ghi gốc, rồi dựng trạng thái hiện tại qua projection. Đổi lại khả năng audit và replay là chi phí versioning, vận hành và phục hồi.

Hãy hình dung một cuốn sổ ở văn phòng mà không ai được tẩy dòng cũ. Mỗi việc quan trọng xảy ra thì ghi thêm một dòng: cuộc họp bắt đầu, quyết định được đổi, hóa đơn được duyệt, deployment phải rollback. Muốn biết tình hình hiện tại, ta đọc các dòng theo thứ tự. Nhưng bản thân cuốn sổ không cố trở thành “trạng thái mới nhất”; nó giữ lại con đường đã tạo ra trạng thái ấy. Đó là lối vào dễ hiểu nhất của Event Sourcing.

Phần lớn ứng dụng lưu câu trả lời hiện tại. Đơn hàng đã được thanh toán, hồ sơ có địa chỉ này, gói thuê bao dùng phương án kia. Khi có thay đổi, bản ghi được cập nhật và giá trị cũ biến mất, trừ khi nhóm ghi audit log ở nơi khác. Cách làm ấy đơn giản và thường hoàn toàn đúng. Rất nhiều hệ thống chỉ cần trả lời: ngay lúc này điều gì là sự thật?

Event Sourcing đổi câu hỏi thành: chuyện gì đã xảy ra? Thay vì chỉ giữ trạng thái hiện tại, hệ thống lưu một chuỗi event như OrderPlaced, PaymentReceived, AddressChanged, ShipmentCreated hay SubscriptionCancelled. Trạng thái hiện tại được dựng bằng cách áp dụng từng event theo thứ tự. Đơn hàng đã thanh toán vì PaymentReceived đã đến sau OrderPlaced; địa chỉ đổi vì sau đó có AddressChanged.

Dòng thời gian chỉ ghi nối tiếp bốn sự kiện OrderPlaced, PaymentConfirmed, Packed và Shipped.
Event Sourcing giữ nguyên chuyện đã xảy ra theo thứ tự, rồi suy ra trạng thái hiện tại từ lịch sử ấy.

Điểm gây lạ lúc đầu là bản ghi trong database không còn là nguồn sự thật chính. Trong hệ thống event-sourced, event stream mới là phần cần được bảo vệ. Projection, read model, dashboard hay search index đều là các góc nhìn suy ra từ stream đó. Chúng phục vụ việc đọc rất tốt, và về nguyên tắc có thể dựng lại khi cần. Lịch sử event mới là thứ không được làm mất.

Khả năng truy vết là lợi ích rõ nhất, nhưng chưa phải tất cả. Event Sourcing cho phép trả lời những câu hỏi mà cách lưu trạng thái cuối thường bỏ qua: thay đổi diễn ra lúc nào, ai kích hoạt, chuỗi sự kiện trước lỗi gồm những gì, và nếu replay lịch sử qua projection mới thì kết quả sẽ ra sao. Khả năng này có giá trị trong các lĩnh vực mà đường đi quan trọng không kém điểm đến, như tài chính, kho hàng, đặt chỗ, bồi thường, luồng phê duyệt, tuân thủ hay hệ thống cộng tác.

Sơ đồ đưa event stream qua projector để tạo ra một read model có thể dựng lại.
Read model hữu ích nhưng có thể bỏ đi và dựng lại; event stream mới là phần được giữ bền vững.

Cách đặt tên event cũng buộc nhóm nghĩ rõ hơn về nghiệp vụ. Event nên nói một sự thật bằng ngôn ngữ nghiệp vụ, không phải mô tả thao tác kỹ thuật. UserUpdated quá chung vì che mất ý nghĩa. EmailChanged, CreditLimitApproved hay RefundRequested kể câu chuyện rõ hơn. Nhiều năm sau, người đọc stream vẫn có thể hiểu việc gì đã xảy ra mà không cần mở UI hoặc đoán từ tên cột.

Đổi lại là một gói trách nhiệm khá nặng. Event schema sẽ tiến hóa; tên event đặt dở sẽ nằm trong lịch sử rất lâu. Replay hàng triệu event có thể chậm nếu không có snapshot. Projection có thể bị lag, và lỗi trong logic dựng projection có thể buộc nhóm dựng lại read model. Hệ thống còn phải xử lý idempotency, ordering, concurrency, versioning và recovery. Event Sourcing không phải món trang trí để sơ đồ kiến trúc trông “xịn” hơn; nó là một mô hình lưu trữ đòi hỏi năng lực vận hành tương xứng.

Ba ô minh họa event, projection được dựng lại và side effect bị khóa trong lúc replay.
Replay dùng để dựng lại projection, không phải gửi lại email, thu tiền lại hay lặp side effect ra bên ngoài.

Một phân biệt quan trọng: Event Sourcing không đồng nghĩa với “hệ thống có dùng event”. Ứng dụng hoàn toàn có thể cập nhật database theo cách thông thường rồi publish một event, nhưng vẫn không phải event-sourced. Chỉ khi event là bản ghi được lưu bền vững chính và trạng thái hiện tại được suy ra từ nó thì mới là Event Sourcing. Khác biệt này kéo theo kỳ vọng vận hành lớn hơn nhiều.

Cách này hay đi cùng CQRS vì đường ghi và đường đọc bắt đầu có nhu cầu khác nhau. Write side kiểm tra command rồi append event. Read side dựng projection thuận tiện cho truy vấn. Màn hình hỗ trợ khách hàng không cần replay toàn bộ stream mỗi lần mở; nó đọc read model đã chuẩn bị sẵn. Sự tách này có thể làm nghiệp vụ phức tạp sáng rõ hơn, nhưng cũng đủ sức biến một CRUD app đơn giản thành cỗ máy nặng nề không cần thiết.

Muốn biết có nên dùng hay không, hãy nhìn vào nỗi đau thật. Nghiệp vụ có thường xuyên hỏi “vì sao giá trị này đổi” không? Lịch sử audit có nằm ở trung tâm không? Các bước chuyển trạng thái có phong phú không? Việc dựng nhiều góc nhìn từ lịch sử có tạo ra giá trị không? Nếu có, Event Sourcing đáng được nghiên cứu nghiêm túc. Nếu hệ thống chủ yếu sửa vài bản ghi đơn giản và chẳng ai cần câu chuyện phía sau giá trị mới nhất, relational model bình thường cùng audit field tử tế thường dễ sống hơn.

Cách học an toàn không phải mang Event Sourcing đi thiết kế lại mọi thứ. Chọn một bài toán nhỏ nơi lịch sử thật sự quan trọng. Viết event bằng ngôn ngữ đời thường, dựng một projection, cố ý làm nó hỏng rồi replay. Khi đó các vấn đề versioning, idempotency và recovery sẽ hiện ra cụ thể hơn nhiều so với một sơ đồ đẹp.

Event Sourcing là lời hứa rằng hệ thống sẽ nhớ mình đã đi tới đây bằng cách nào. Lời hứa ấy có thể cực kỳ đáng giá, nhưng không nên đưa ra nhẹ tay. Điều đáng cân nhắc không chỉ là lúc lịch sử cứu được một cuộc điều tra, mà cả ngày nhóm phải trả chi phí để lịch sử ấy luôn replay được và còn mang đúng nghĩa.

Bạn thấy bài viết thế nào?