Chuyển từ dữ liệu quan hệ sang sự kiện
(event-driven.io)- Mô hình CRUD quan hệ cho thấy rõ cấu trúc lưu trữ, nhưng dễ ghi đè lên quy trình nghiệp vụ, khiến khó theo dõi điều gì thực sự đã xảy ra trong hệ thống
- Event Sourcing lưu lại các sự kiện bất biến phát sinh sau mỗi thao tác dưới dạng Event Stream, rồi đọc danh sách đó trong các quyết định về sau để xác định trạng thái hiện tại
- Việc mô hình hóa bắt đầu bằng cách tìm ra các sự kiện trước, sau đó liên kết lệnh (command) là ý định hành động của người dùng với các quy tắc nghiệp vụ để hiểu quy trình
- Khi tìm ứng viên sự kiện từ dữ liệu quan hệ hiện có, có thể xem các cột trạng thái, cột ngày tháng, tính nullable và quan hệ 1:N, nhưng sẽ rất rủi ro nếu cho rằng chỉ từ giá trị trạng thái có thể khôi phục được lịch sử đầy đủ
- Khi di chuyển dữ liệu chỉ còn trạng thái cuối cùng, thực tế hơn là bắt đầu bằng một sự kiện import tường minh như Order Imported thay vì cố tái dựng các sự kiện trong quá khứ, đồng thời kiểm chứng lặp lại trong môi trường an toàn
Nhìn từ dữ liệu CRUD sang mô hình lấy sự kiện làm trung tâm
- Mô hình dữ liệu quan hệ cho thấy dữ liệu nào đang được lưu trữ, nhưng khó nắm được điều gì đã xảy ra bên trong hệ thống và quy trình đã tương tác như thế nào
- Cách làm CRUD truyền thống có thể làm mất thông tin nghiệp vụ quan trọng khi ghi đè dữ liệu
- Event Sourcing ưu tiên chất lượng thông tin hơn kích thước lưu trữ, và lưu các sự thật phát sinh sau mỗi thao tác dưới dạng sự kiện
Mô hình cơ bản của Event Sourcing
- Sự kiện là sự thật về điều đã xảy ra, và là dữ liệu bất biến không thể thay đổi sau khi đã được lưu
- Event Stream là danh sách chứa mọi việc đã xảy ra với một bản ghi theo thứ tự thời gian
- Không thể sửa các sự kiện trong quá khứ, nhưng có thể thêm một sự kiện mới ở cuối để sửa sai cho lỗi trước đó
- Khi ra quyết định, hệ thống đọc và kiểm tra danh sách sự kiện để xác định trạng thái hiện tại và hành động tiếp theo
Thứ tự mô hình hóa quy trình
- Việc mô hình hóa bắt đầu trước tiên từ khám phá sự kiện
- Sau đó tìm các lệnh (command) để định nghĩa ý định thực hiện hành động nào
- Cuối cùng là sắp xếp các quy tắc nghiệp vụ
- Sự kiện trở thành trục trung tâm giúp cả bên kỹ thuật và bên nghiệp vụ cùng hiểu quy trình
- Có thể nắm bắt quy trình bằng cách xem cùng lúc sự kiện, lệnh và quy tắc như trong EventStorming của Alberto Brandolini
Tìm ứng viên sự kiện từ dữ liệu quan hệ hiện có
-
1. Xem các cột trạng thái
- Giá trị trong cột status có thể phản ánh các giai đoạn trong vòng đời dữ liệu
- Nếu đơn hàng có các trạng thái như initiated, shipped, paid thì mỗi trạng thái có thể là ứng viên cho các sự kiện Order Initiated, Order Shipped, Order Paid
- Tuy vậy, giá trị trạng thái có thể chỉ là cách diễn giải đã làm phẳng quy trình nghiệp vụ, nên không được giả định là đầy đủ
- Nên tránh đặt tên sự kiện theo thao tác CRUD như Order Created, Order Updated, Order Deleted
- State Obsession được nêu như một cách làm cần tránh
-
2. Kiểm tra các cột ngày tháng
- Các cột ngày tháng có thể cho biết thời điểm phát sinh quan trọng trong vòng đời quy trình
- CreatedDate và ModifiedDate không cung cấp nhiều thông tin, nhưng ShipmentDate, DeliveryDate, OrderPlacementDate là những gợi ý tốt hơn
- Ví dụ:
- ShipmentDate có thể là manh mối để đưa vào sự kiện Order Shipped
- OrderPlacementDate gợi ý rằng Order Placed có thể là tên tốt hơn Order Initiated
- DeliveryDate cho thấy có thể cần sự kiện Order Delivered
- Những gợi ý này cần được xác nhận với chuyên gia domain để khớp với quy trình nghiệp vụ thực tế
-
3. Phân tích tính nullable của cột
- Cột non-nullable là dữ liệu luôn phải được cung cấp
- Cột nullable có thể là dữ liệu được bổ sung ở thao tác khác về sau hoặc là giá trị tùy chọn
- Trong Ordering Process, nếu một cột là bắt buộc thì dữ liệu đó cũng phải nằm trong sự kiện Order Initiated đầu tiên
- Không phải lúc nào một loại sự kiện đơn lẻ cũng là điểm bắt đầu của stream; có thể có nhiều sự kiện khởi đầu
-
4. Tìm các bảng có nhiều quan hệ 1:N
- Để tìm ranh giới của stream, có thể bắt đầu từ các bảng có nhiều quan hệ 1:N
- Các bảng có nhiều quan hệ “one” là ứng viên cho kiểu stream
- Cũng cần đánh giá về mặt logic xem dữ liệu có thể tồn tại độc lập với nhau hay không
- shipment có thể là một quy trình tách biệt với order
- order line khó có thể tồn tại nếu không có order
- Trong quá trình thảo luận về ranh giới, có thể phát hiện thêm nhiều sự kiện và mở rộng hiểu biết về quy trình
Không tạo ra các sự kiện giả khi migration
- Dữ liệu quan hệ là trạng thái cuối cùng đã bị làm phẳng, nên nếu chỉ nhìn vào trạng thái đó để suy ngược các sự kiện chi tiết thực sự đã diễn ra trong quá khứ thì có thể thất bại hoặc không chính xác
- Thay vì cố tạo ra các sự kiện quá khứ ở mức quá nhỏ, tốt hơn là cung cấp tường minh một sự kiện Order Imported chứa toàn bộ trạng thái hiện tại và mã diễn giải
- Sự kiện import cho thấy rõ dữ liệu đã được đưa vào theo cách nào, và điều này có thể rất quan trọng cho việc xử lý sự cố và chẩn đoán
Kiểm chứng bằng prototype
- Việc migration nên được thử trước bằng prototype trong môi trường an toàn để xác nhận mô hình thực sự vận hành như thế nào
- Cần lặp lại việc chỉnh sửa bằng cách so sánh kết quả với giá trị kỳ vọng
- Cần một cách làm không vội vàng, không làm mất thông tin hiện có, đồng thời cải thiện mô hình về sau dựa trên chính thông tin đó
- Chiến lược chung để chuyển từ dữ liệu quan hệ sang tài liệu cũng có liên hệ với General strategy for migrating relational data to document-based
1 bình luận
Ý kiến trên Hacker News
2c: nếu các phần khác của ứng dụng cũng cần PostgreSQL, thì tốt hơn hết là lưu cả dữ liệu sự kiện vào PostgreSQL + công cụ báo cáo FOSS (Apache Superset, Metabase, v.v.) và chịu tải đến khoảng 2TB
Sau đó có thể quyết định xem có cần giữ toàn bộ 2TB luôn trực tuyến hay không, hay chỉ cần bản tóm tắt theo ngày/giờ. Nếu là vế sau thì tiếp tục dùng PostgreSQL vẫn là đủ[1]
Một khách hàng xử lý quy mô 10TB+, 1.500 sự kiện mỗi giây, 600 byte mỗi bản ghi (80GB/ngày trước khi lập chỉ mục), chỉ giữ trực tuyến 2 ngày dữ liệu chi tiết, phần còn lại được tổng hợp và chuyển chi tiết sang S3 để tiếp tục truy vấn bằng Athena SQL[2]
Tổng chi phí, kể cả cổng báo cáo cho khách hàng, dưới 2.000 USD, và trên AWS RDS multi-AZ automatic failover (db.m7g.2xlarge), cả truy vấn ghi lẫn truy vấn báo cáo đều được xử lý với mức tải dưới 2%
Vì đội ngũ kinh doanh tự tạo biểu đồ và đồ thị nên chỉ cần 1 kỹ sư dành chưa đến 5 giờ mỗi tháng cho việc bảo trì
Nếu dùng công cụ độc quyền thì sẽ có sẵn vài biểu đồ “đi kèm”, nhưng khi dùng pgsql thì dữ liệu ở một nơi, chỉ có một hệ thống cần học, một hệ thống cần duy trì trực tuyến/nhân bản/sao lưu/khôi phục, một hệ thống cần bảo mật/mở rộng, một nhà cung cấp cần quản lý, và có hàng triệu kỹ sư biết hệ thống này
Với các hệ thống như Preset hay Metabase, tạo 12 biểu đồ chỉ mất một giờ, và cả người không chuyên kỹ thuật cũng làm được
Nói trước là tôi có thiên kiến, nhưng trong hơn 20 năm tôi đã thấy các hệ thống cơ sở dữ liệu và báo cáo sinh ra rồi biến mất, còn PostgreSQL tốt già cỗi thì mỗi năm lại càng tốt hơn
https://instances.vantage.sh/aws/rds/db.m7g.2xlarge?region=u...
[1] Nếu thực sự cần thì vẫn có các hệ thống tương thích PostgreSQL để mở rộng thêm. Aurora có thể mở rộng khoảng 3~5 lần, TimescaleDB khoảng 10 lần, CitusDB khoảng 10 lần+. Mỗi cái đều phải đánh đổi bằng việc trở nên hơi phi tiêu chuẩn, nên tôi không khuyến nghị trước khi thực sự cần
[2] Dashboard báo cáo cho khách hàng cần phản hồi dưới 1 giây, và PostgreSQL đáp ứng việc này bằng cách truy vấn các bảng tổng hợp đã được lập chỉ mục. Athena phản hồi trong khoảng 1~2 giây nhờ quét song song
Có thể giữ snapshot của dữ liệu trước khi lưu, có script để nhận diện và thu thập một chuỗi sự kiện cụ thể, rồi để con người xem xét và áp dụng hồi tố hiệu ứng của logic mới trên diện rộng
Các công cụ như https://django-simple-history.readthedocs.io/en/latest/ là giải pháp đơn giản và khá đáng tin để tạo bảng kiểm toán, và nếu cần kiểm toán cả truy cập trực tiếp vào cơ sở dữ liệu thì có thể thêm trigger Postgres
Về mặt lý thuyết tôi thích event sourcing, nhưng trên thực tế nó đòi hỏi quá nhiều boilerplate để có thể nhanh chóng và ổn định triển khai các luồng CRUD mới hoặc các can thiệp/hotfix mà startup giai đoạn đầu~giữa thường phải làm trong những tình huống bất ngờ
Trừ khi bạn đang làm việc kiểu triển khai rail xử lý thanh toán, event sourcing có thể không phải là lựa chọn phù hợp
https://news.ycombinator.com/item?id=17817375 (2018) cũng có một cuộc thảo luận hay về nhược điểm của event sourcing
Vấn đề duy nhất của PostgreSQL là phía ghi có vài vấn đề mở rộng khá thú vị. Thường người ta khuyên nên đặt một hàng đợi giữa nguồn sự kiện và DB
{id:uuid,created_at:timestamptz,data:jsonb}không nhỉĐặc biệt khi cấu trúc sự kiện rất đa dạng và định nghĩa sự kiện thay đổi dần theo thời gian thì rất khó tận dụng đúng khả năng lập chỉ mục JSONB
Có lẽ tôi nên đọc kỹ hơn tài liệu này: https://www.postgresql.org/docs/current/datatype-json.html#J...
Trước đây nhóm từng cân nhắc rất kỹ event sourcing, nhưng với tôi nó giống như một giải pháp đi tìm vấn đề
Có thể nó cũng vận hành được với chúng tôi, nhưng lợi ích không rõ ràng ngay lập tức, còn rủi ro và những lần thử-sai khi áp dụng cách làm mới lại không có vẻ là lựa chọn tốt nhất cho dự án hay công ty, nên cuối cùng đã bỏ qua
Có thể đó là một quyết định kiểu bỏ lỡ một công cụ đáng để học hỏi, nhưng vì cũng không phải đang bị cáo đuổi phía sau nên tôi không hối tiếc vì đã không chui xuống cái hang thỏ đó
“Vấn đề” mà giải pháp này giải quyết chính là điều đó
Nhưng trong đa số trường hợp, chỉ cần dùng cơ sở dữ liệu thông thường và lưu lịch sử thay đổi trước đây vào một bảng phụ là đủ. Khi đó cơ sở dữ liệu chính hoạt động như một kiểu materialized view
Tôi cũng không phàn nàn gì lớn, và cũng không đến mức cho rằng đó là một lựa chọn sai, nhưng cách chúng ta xử lý thay đổi mô hình dữ liệu thì có vấn đề
Có cảm giác phần lớn cách lưu trữ dữ liệu không theo kịp cách phần mềm được xây dựng ngày nay, còn những thứ như event và queue là kết quả của việc chồng thêm chức năng cần thiết lên trên các hệ thống sẵn có
Ngày nay, nhiều quan hệ dữ liệu diễn ra giữa nhiều service, tức là bên ngoài cơ sở dữ liệu. Môi trường CNTT hiện đại ở nhiều tổ chức được dựng lên như vậy
Có dữ liệu chủ nội bộ hỗ trợ nhiều nhóm nghiệp vụ, rồi để đơn giản hóa công việc thì nó tương tác với hơn 300 hệ thống CNTT và ứng dụng
Dùng microservice thì dễ giữ business logic và mô hình dữ liệu gọn gàng, nhưng đổi lại phải quản lý event, queue, trạng thái dữ liệu và cả các kho lưu trữ phụ thuộc, mà hiện giờ điều đó quá phức tạp
Tôi thích SQL, nhưng thành thật mà nói các hệ thống chúng ta đang làm ngày nay có lẽ gần như chỉ cần nhét vào SQLite là đủ
Điều còn thiếu trong các cuộc thảo luận kiểu này là khi nào thì kiến trúc hướng sự kiện thực sự phù hợp
Nói ngắn gọn, nếu khách hàng làm điều gì đó và chờ phản hồi thì đó không phải hướng sự kiện mà chỉ là request/response thông thường
Hướng sự kiện là khi có điều gì đó xảy ra ngoài băng. Ví dụ như push code lên GitHub rồi build được kích hoạt
Trong ví dụ này, việc refresh trang để xem mã đã cập nhật là request/response, còn bản build CI được đưa vào queue thì là hướng sự kiện
Hy vọng điều này hữu ích
Ngay cả với event sourcing hay hướng sự kiện, bạn vẫn có thể tạo ra luồng request-response, inline, blocking và tuần hoàn
Ngược lại, ngay cả khi không dùng event sourcing hay hướng sự kiện, bạn vẫn có thể làm bất đồng bộ rất tốt bằng worker, queue, actor hay multithreading
Việc mô hình hóa domain event hữu ích cho việc giải thích vấn đề cần giải quyết với chuyên gia miền nghiệp vụ, và có lẽ phù hợp ở mức ghi lại trong tài liệu khi lập kế hoạch giải pháp
Nếu thực sự cần triển khai một hệ thống cung cấp audit trail cho một state machine tồn tại dài hạn, thì có khả năng dùng Temporal.io hoặc durable functions sẽ tốt hơn
Các công cụ này dùng event sourcing cho tính bền vững nội bộ, đồng thời cung cấp một mô hình lập trình áp đặt các ràng buộc khác nhau lên mã điều phối chức năng (workflows) và mã tương tác với thế giới thực (activities), buộc người phát triển phải nghĩ đến việc khử trùng lặp và tính idempotent
Tôi muốn nghe các gợi ý về cách vượt qua vấn đề này
Ý tưởng thì thú vị, nhưng bài viết không giải thích rõ cách nó hoạt động
Tôi thắc mắc làm sao để tái dựng trạng thái hiện tại một cách hiệu quả từ event stream, và event stream được mô hình hóa trong cơ sở dữ liệu như thế nào
https://www.youtube.com/watch?v=gG6DGmYKk4I
https://www.youtube.com/watch?v=jnDchr5eabI
https://www.youtube.com/watch?v=ArcypYS5XBQ
https://www.youtube.com/watch?v=uODSwR2CIV4
Ngoài ra còn duy trì các ví dụ mẫu trên GitHub
https://github.com/oskardudycz/EventSourcing.NetCore
https://github.com/oskardudycz/EventSourcing.NodeJS
https://github.com/oskardudycz/EventSourcing.JVM
Thứ nhất là dùng cơ sở dữ liệu được thiết kế cho mục đích này. Có thể kể đến Google BigQuery, Amazon Redshift, ClickHouse
Mọi dữ liệu hiện tại về bản chất đều là một dạng tổng hợp. Nói cách khác, nó giống một truy vấn group-by trên cơ sở dữ liệu sự kiện
Nếu có các sự kiện thì về mặt kỹ thuật có thể tái tạo lại trạng thái hiện tại hoặc trạng thái trong quá khứ bằng truy vấn tổng hợp, nên cách này có lý
Thứ hai là đổi tên kho lưu trữ quan hệ và gọi nó là lớp cache nằm bên cạnh hệ thống sự kiện
Về mặt chức năng thì vẫn là cùng một thứ, nhưng nó không làm bật đèn cảnh báo với những người ám ảnh việc mọi thứ phải hoàn toàn event-driven
Kiến trúc được mô tả trong bài thực sự tồn tại. Chỉ là nó cực kỳ phức tạp, nên các dịch vụ sử dụng nó thường làm những việc rất chuyên biệt. Hãy nghĩ đến Google Analytics, Datadog, Splunk
Bạn có thể tạo các trạng thái khác nhau trong các hệ thống khác nhau tùy theo từng yêu cầu
Nếu xây dựng một hệ thống mua sắm, khi có giao dịch mua và khách hàng, một dịch vụ có thể đọc sự kiện để tạo bảng quan hệ cho mục đích tài chính
Một dịch vụ khác có thể đọc sự kiện để tạo kho key-value cho dữ liệu khách hàng, và dịch vụ thứ ba có thể vận hành dịch vụ OpenSearch cho tìm kiếm sản phẩm
Event stream là một danh sách. Nếu dùng thứ phù hợp với mục đích như Kafka, thì sẽ có nhiều danh sách, tức là topic, partition, v.v.
Nhưng ngay cả vậy, nó vẫn có thể được giải quyết trong mô hình quan hệ
Đây là khác biệt giữa top-down vs bottom-up, hoặc giữa tùy biến và phổ dụng
Top-down là bắt đầu từ miền nghiệp vụ rồi ánh xạ cách triển khai lên các công nghệ, công cụ, nhà cung cấp sẵn có
Bottom-up là bắt đầu từ các công nghệ, công cụ, nhà cung cấp sẵn có, rồi ghép chúng lại để tạo ra một giải pháp hoạt động được
Phía tùy biến có DDD, CQRS/ES, Sagas, TBUI (UI dựa trên/được dẫn dắt bởi tác vụ), GraphQL, kiểu dữ liệu đại số
Phía phổ dụng có RDBMS, CRUD, REST, giao dịch ACID, CDC, UI quản trị phổ thông, no-code/low-code, kiểu bị hạn chế/kiểu phổ dụng
Tôi sẽ cứ tiếp tục dùng dữ liệu quan hệ kiểu cũ mà tốt
Tôi đồng ý với kiến trúc hướng sự kiện, nhưng bài này có vẻ gặp khó trong việc truyền đạt ý chính
Tôi sẽ tập trung vào sự khác biệt giữa quan hệ dữ liệu và hành vi nghiệp vụ
Khi bắt đầu suy nghĩ theo góc nhìn hành vi và hoạt động nghiệp vụ, việc dịch chuyển ra xa kho dữ liệu quan hệ phục vụ vận hành sẽ trở nên rõ ràng hơn nhiều
Event sourcing có nhiều đặc tính tốt nên khá thú vị
Nhưng rốt cuộc vẫn cần quan hệ đúng không? Nếu vậy thì các quan hệ đó được triển khai như thế nào?
Nếu câu trả lời là “mọi thứ đều được ngầm nhét vào code ở tầng ứng dụng” thì khó mà chấp nhận
Dù sao vẫn cần truy vấn các quan hệ, hoặc giữ cho các view quan hệ luôn được cập nhật, hay một thứ gì đó tương tự
Quan hệ không nhất thiết phải là cốt lõi của mô hình lưu trữ bền vững, nhưng nó phải được triển khai ở đâu đó trong tầng dữ liệu, mà ở đây tôi không thấy nhắc đến
Firestore cũng có vấn đề tương tự. Ai cũng xử lý quan hệ theo cách nào đó, nhưng cuối cùng lại thành code ứng dụng spaghetti không mở rộng nổi
Nếu quen với lập trình hàm, thì về bản chất điều này giống phép fold gộp event stream thành một trạng thái
Theo kinh nghiệm từng làm với hệ thống event sourcing trước đây, ưu điểm là có lịch sử sự kiện được lưu tường minh, nhưng độ phức tạp cũng tăng lên đáng kể
Sẽ phát sinh các vấn đề như thực sự tạo read model thế nào, quản lý phiên bản mô hình ra sao, có nên đặt snapshot cho read model hay không
Theo kinh nghiệm của tôi, trong hầu hết bối cảnh áp dụng mẫu này thì độ phức tạp bổ sung không xứng đáng
Thứ cần thiết là hàng đợi lệnh. Event lệnh không phải là event miền nghiệp vụ