Sử dụng RDBMS làm bộ nhớ tìm nguồn cung ứng sự kiện


119

Nếu tôi đang sử dụng RDBMS (ví dụ: SQL Server) để lưu trữ dữ liệu tìm nguồn sự kiện, lược đồ có thể trông như thế nào?

Tôi đã thấy một vài biến thể được nói đến theo nghĩa trừu tượng, nhưng không có gì cụ thể.

Ví dụ: giả sử một người có thực thể "Sản phẩm" và các thay đổi đối với sản phẩm đó có thể ở dạng: Giá, Chi phí và Mô tả. Tôi bối rối về việc liệu tôi có nên:

  1. Có bảng "ProductEvent", có tất cả các trường cho một sản phẩm, trong đó mỗi thay đổi có nghĩa là một bản ghi mới trong bảng đó, cộng với "ai, cái gì, ở đâu, tại sao, khi nào và như thế nào" (WWWWWH). Khi chi phí, giá cả hoặc mô tả được thay đổi, một hàng hoàn toàn mới sẽ được thêm vào để đại diện cho Sản phẩm.
  2. Lưu trữ Chi phí, Giá và Mô tả sản phẩm trong các bảng riêng biệt được nối với bảng Sản phẩm có mối quan hệ khóa ngoài. Khi các thay đổi đối với các thuộc tính đó xảy ra, hãy viết các hàng mới với WWWWWH nếu thích hợp.
  3. Lưu trữ WWWWWH, cùng với một đối tượng được tuần tự hóa đại diện cho sự kiện, trong bảng "ProductEvent", có nghĩa là bản thân sự kiện phải được tải, hủy tuần tự hóa và phát lại trong mã ứng dụng của tôi để tạo lại trạng thái ứng dụng cho một Sản phẩm nhất định .

Riêng tôi lo lắng về phương án 2 ở trên. Nhìn chung, bảng sản phẩm sẽ gần như là một bảng cho mỗi thuộc tính, nơi để tải Trạng thái ứng dụng cho một sản phẩm nhất định sẽ yêu cầu tải tất cả các sự kiện cho sản phẩm đó từ mỗi bảng sự kiện sản phẩm. Vụ nổ bàn này có mùi không đúng với tôi.

Tôi chắc chắn rằng "điều đó còn tùy thuộc", và mặc dù không có "câu trả lời đúng" duy nhất, tôi đang cố gắng cảm nhận điều gì có thể chấp nhận được và điều gì hoàn toàn không thể chấp nhận được. Tôi cũng biết rằng NoSQL có thể trợ giúp ở đây, nơi các sự kiện có thể được lưu trữ dựa trên một gốc tổng hợp, nghĩa là chỉ một yêu cầu duy nhất đến cơ sở dữ liệu để nhận các sự kiện để xây dựng lại đối tượng, nhưng chúng tôi không sử dụng db NoSQL tại thời điểm nên tôi đang tìm kiếm các lựa chọn thay thế.


2
Ở dạng đơn giản nhất của nó: [Sự kiện] {AggregateId, AggregateVersion, EventPayload}. Không cần loại tổng hợp, nhưng bạn CÓ THỂ tùy chọn lưu trữ nó. Không cần loại sự kiện, nhưng bạn CÓ THỂ tùy chọn lưu trữ nó. Đó là một danh sách dài những thứ đã xảy ra, bất cứ thứ gì khác chỉ là tối ưu hóa.
Yves Reynhout

7
Chắc chắn tránh xa # 1 và # 2. Tuần tự hóa mọi thứ thành một đốm màu và lưu trữ theo cách đó.
Jonathan Oliver

Câu trả lời:


109

Kho sự kiện không cần biết về các trường hoặc thuộc tính cụ thể của các sự kiện. Nếu không, mọi sửa đổi mô hình của bạn sẽ dẫn đến việc phải di chuyển cơ sở dữ liệu của bạn (giống như trong trường hợp bền bỉ dựa trên trạng thái kiểu cũ). Do đó, tôi sẽ không đề xuất tùy chọn 1 và 2.

Dưới đây là lược đồ được sử dụng trong Ncqrs . Như bạn có thể thấy, bảng "Sự kiện" lưu trữ dữ liệu liên quan dưới dạng CLOB (tức là JSON hoặc XML). Điều này tương ứng với tùy chọn 3 của bạn (Chỉ có điều là không có bảng "ProductEvents" vì bạn chỉ cần một bảng "Sự kiện" chung. Trong Ncqrs, ánh xạ tới Gốc tổng hợp của bạn xảy ra thông qua bảng "Nguồn sự kiện", trong đó mỗi Nguồn sự kiện tương ứng với một thực tế Gốc tổng hợp.)

Table Events:
    Id [uniqueidentifier] NOT NULL,
    TimeStamp [datetime] NOT NULL,

    Name [varchar](max) NOT NULL,
    Version [varchar](max) NOT NULL,

    EventSourceId [uniqueidentifier] NOT NULL,
    Sequence [bigint], 

    Data [nvarchar](max) NOT NULL

Table EventSources:
    Id [uniqueidentifier] NOT NULL, 
    Type [nvarchar](255) NOT NULL, 
    Version [int] NOT NULL

Cơ chế bền vững SQL của việc triển khai Cửa hàng sự kiện của Jonathan Oliver về cơ bản bao gồm một bảng được gọi là "Cam kết" với trường BLOB "Tải trọng". Điều này khá giống với trong Ncqrs, chỉ khác là nó tuần tự hóa các thuộc tính của sự kiện ở định dạng nhị phân (ví dụ: hỗ trợ thêm mã hóa).

Greg Young đề xuất một cách tiếp cận tương tự, như được tài liệu rộng rãi trên trang web của Greg .

Lược đồ của bảng "Sự kiện" nguyên mẫu của anh ấy có nội dung:

Table Events
    AggregateId [Guid],
    Data [Blob],
    SequenceNumber [Long],
    Version [Int]

9
Câu trả lời hay! Một trong những đối số chính mà tôi tiếp tục đọc về việc sử dụng EventSourcing là khả năng truy vấn lịch sử. Làm cách nào để tạo một công cụ báo cáo hiệu quả trong việc truy vấn khi tất cả dữ liệu thú vị được tuần tự hóa dưới dạng XML hoặc JSON? Có bất kỳ bài báo thú vị nào đang tìm kiếm một giải pháp dựa trên bảng không?
Marijn Huizendveld

11
@MarijnHuizendveld có thể bạn không muốn truy vấn chính kho sự kiện. Giải pháp phổ biến nhất sẽ là kết nối một vài trình xử lý sự kiện chiếu các sự kiện vào cơ sở dữ liệu báo cáo hoặc BI. Phát lại lịch sử sự kiện với những trình xử lý này.
Dennis Traub

1
@Denis Traub cảm ơn câu trả lời của bạn. Tại sao không truy vấn ngược lại kho sự kiện? Tôi e rằng nó sẽ trở nên khá lộn xộn / dữ dội nếu chúng tôi phải phát lại toàn bộ lịch sử mỗi khi chúng tôi đưa ra một trường hợp BI mới?
Marijn Huizendveld

1
Tôi nghĩ tại một số điểm, bạn cũng phải có các bảng bên cạnh kho sự kiện, để lưu trữ dữ liệu từ mô hình ở trạng thái mới nhất? Và bạn chia mô hình thành mô hình đọc và mô hình ghi. Mô hình ghi đi ngược lại với cửa hàng sự kiện và các cửa hàng sự kiện sẽ cập nhật mô hình đọc. Mô hình đọc chứa các bảng đại diện cho các thực thể trong hệ thống của bạn - vì vậy bạn có thể sử dụng mô hình đọc để báo cáo và xem. Chắc tôi đã hiểu lầm điều gì đó.
theBoringCoder

10
@theBoringCoder Có vẻ như bạn đã nhầm lẫn giữa Tìm nguồn cung ứng sự kiện và CQRS hoặc ít nhất là đã bị nghiền nát trong đầu. Chúng thường được tìm thấy cùng nhau nhưng chúng không giống nhau. CQRS cho phép bạn tách các mô hình đọc và ghi của mình trong khi Tìm nguồn cung ứng Sự kiện cho phép bạn sử dụng một luồng sự kiện làm nguồn chân lý duy nhất trong ứng dụng của mình.
Bryan Anderson

7

Dự án GitHub CQRS.NET có một vài ví dụ cụ thể về cách bạn có thể thực hiện EventStores trong một vài công nghệ khác nhau. Tại thời điểm viết bài, có một triển khai trong SQL sử dụng Linq2SQL và một lược đồ SQL đi kèm với nó, có một cho MongoDB , một cho DocumentDB (CosmosDB nếu bạn đang ở Azure) và một sử dụng EventStore (như đã đề cập ở trên). Có nhiều hơn trong Azure như Bộ nhớ Bảng và Bộ nhớ Blob, rất giống với bộ nhớ tệp phẳng.

Tôi đoán điểm chính ở đây là tất cả chúng đều tuân theo cùng một hợp đồng / nguyên tắc. Tất cả chúng đều lưu trữ thông tin ở một nơi / vùng chứa / bảng duy nhất, chúng sử dụng siêu dữ liệu để xác định một sự kiện này từ một sự kiện khác và 'chỉ' lưu trữ toàn bộ sự kiện như nó vốn có - trong một số trường hợp được tuần tự hóa, trong các công nghệ hỗ trợ. Vì vậy, tùy thuộc vào việc bạn chọn cơ sở dữ liệu tài liệu, cơ sở dữ liệu quan hệ hay thậm chí là tệp phẳng, có một số cách khác nhau để tất cả đều đạt được mục đích giống nhau của một kho sự kiện (sẽ hữu ích nếu bạn thay đổi ý định bất kỳ lúc nào và thấy bạn cần di chuyển hoặc hỗ trợ nhiều hơn một công nghệ lưu trữ).

Là một nhà phát triển của dự án, tôi có thể chia sẻ một số hiểu biết sâu sắc về một số lựa chọn mà chúng tôi đã thực hiện.

Đầu tiên, chúng tôi nhận thấy (ngay cả với UUID / GUID duy nhất thay vì số nguyên) vì nhiều lý do ID tuần tự xảy ra vì lý do chiến lược, do đó chỉ có một ID không đủ duy nhất cho một khóa, vì vậy chúng tôi đã hợp nhất cột khóa ID chính của mình với dữ liệu / loại đối tượng để tạo khóa duy nhất thực sự (theo nghĩa ứng dụng của bạn). Tôi biết một số người nói rằng bạn không cần phải lưu trữ nó, nhưng điều đó sẽ phụ thuộc vào việc bạn có phải là greenfield hay phải cùng tồn tại với các hệ thống hiện có.

Chúng tôi đã mắc kẹt với một vùng chứa / bảng / bộ sưu tập vì lý do bảo trì, nhưng chúng tôi đã thử với một bảng riêng biệt cho mỗi thực thể / đối tượng. Trong thực tế, chúng tôi nhận thấy điều đó có nghĩa là ứng dụng cần quyền "CREATE" (nói chung không phải là một ý tưởng hay ... nói chung, luôn có các ngoại lệ / loại trừ) hoặc mỗi khi một thực thể / đối tượng mới ra đời hoặc được triển khai, các thùng chứa / bảng / bộ sưu tập lưu trữ cần được tạo. Chúng tôi nhận thấy điều này rất chậm đối với sự phát triển của địa phương và có vấn đề đối với việc triển khai sản xuất. Bạn có thể không, nhưng đó là trải nghiệm thực tế của chúng tôi.

Một điều khác cần nhớ là việc yêu cầu hành động X xảy ra có thể dẫn đến nhiều sự kiện khác nhau xảy ra, do đó biết tất cả các sự kiện được tạo bởi một lệnh / sự kiện / điều gì đã từng hữu ích. Chúng cũng có thể nằm trên các loại đối tượng khác nhau, ví dụ như đẩy "mua" trong giỏ hàng có thể kích hoạt các sự kiện tài khoản và kho hàng để kích hoạt. Một ứng dụng tiêu thụ có thể muốn biết tất cả những điều này, vì vậy chúng tôi đã thêm một CorrelationId. Điều này có nghĩa là người tiêu dùng có thể yêu cầu tất cả các sự kiện được nêu ra do yêu cầu của họ. Bạn sẽ thấy điều đó trong lược đồ .

Cụ thể với SQL, chúng tôi nhận thấy rằng hiệu suất thực sự trở thành một nút thắt cổ chai nếu các chỉ mục và phân vùng không được sử dụng thích hợp. Hãy nhớ rằng các sự kiện sẽ cần được truyền theo thứ tự ngược lại nếu bạn đang sử dụng ảnh chụp nhanh. Chúng tôi đã thử một vài chỉ mục khác nhau và nhận thấy rằng trong thực tế, cần có một số chỉ mục bổ sung để gỡ lỗi các ứng dụng trong thế giới thực đang sản xuất. Một lần nữa bạn sẽ thấy điều đó trong lược đồ .

Các siêu dữ liệu trong quá trình sản xuất khác rất hữu ích trong quá trình điều tra dựa trên sản xuất, dấu thời gian cho chúng tôi thông tin chi tiết về thứ tự các sự kiện được duy trì và tăng lên. Điều đó đã mang lại cho chúng tôi một số trợ giúp về một hệ thống điều khiển sự kiện đặc biệt nặng nề, nâng cao số lượng lớn sự kiện, cung cấp cho chúng tôi thông tin về hiệu suất của những thứ như mạng và hệ thống phân phối trên mạng.


Thật tuyệt, cảm ơn. Khi nó xảy ra, kể từ khi viết câu hỏi này, tôi đã tự mình xây dựng một vài câu hỏi như một phần của thư viện Inforigami.Regalo trên github. Triển khai RavenDB, SQL Server và EventStore. Tự hỏi về việc thực hiện một tập tin dựa trên một tiếng cười. :)
Neil Barnwell

1
Chúc mừng. Tôi đã thêm câu trả lời chủ yếu cho những người khác đã xem nó trong thời gian gần đây và chia sẻ một số bài học kinh nghiệm, thay vì chỉ kết quả.
cdmdotnet

3

Vâng, bạn có thể muốn xem Datomic.

Datomic là một cơ sở dữ liệu gồm các dữ kiện linh hoạt, dựa trên thời gian , hỗ trợ các truy vấn và tham gia, với khả năng mở rộng linh hoạt và các giao dịch ACID.

Tôi đã viết một câu trả lời chi tiết ở đây

Bạn có thể xem bài nói chuyện của Stuart Halloway giải thích thiết kế của Datomic tại đây

Vì Datomic lưu trữ dữ kiện kịp thời, bạn có thể sử dụng nó cho các trường hợp sử dụng tìm nguồn cung ứng sự kiện và hơn thế nữa.


2

Tôi nghĩ giải pháp (1 & 2) có thể trở thành vấn đề rất nhanh khi mô hình miền của bạn phát triển. Các trường mới được tạo, một số thay đổi ý nghĩa và một số có thể không còn được sử dụng. Cuối cùng thì bảng của bạn sẽ có hàng tá trường có thể làm trống và việc tải các sự kiện sẽ rất lộn xộn.

Ngoài ra, hãy nhớ rằng kho lưu trữ sự kiện chỉ nên được sử dụng để ghi, bạn chỉ truy vấn nó để tải các sự kiện, không phải thuộc tính của tổng hợp. Chúng là những thứ riêng biệt (đó là bản chất của CQRS).

Giải pháp 3 là những gì mọi người thường làm, có nhiều cách để thành công điều đó.

Ví dụ: EventFlow CQRS khi được sử dụng với SQL Server sẽ tạo một bảng với lược đồ này:

CREATE TABLE [dbo].[EventFlow](
    [GlobalSequenceNumber] [bigint] IDENTITY(1,1) NOT NULL,
    [BatchId] [uniqueidentifier] NOT NULL,
    [AggregateId] [nvarchar](255) NOT NULL,
    [AggregateName] [nvarchar](255) NOT NULL,
    [Data] [nvarchar](max) NOT NULL,
    [Metadata] [nvarchar](max) NOT NULL,
    [AggregateSequenceNumber] [int] NOT NULL,
 CONSTRAINT [PK_EventFlow] PRIMARY KEY CLUSTERED 
(
    [GlobalSequenceNumber] ASC
)

Ở đâu:

  • GlobalSequenceNumber : Nhận dạng toàn cục đơn giản, có thể được sử dụng để sắp xếp hoặc xác định các sự kiện bị thiếu khi bạn tạo phép chiếu (mô hình đọc).
  • BatchId : Nhận dạng nhóm sự kiện mà ở đó được chèn nguyên tử (TBH, không biết tại sao điều này lại hữu ích)
  • AggregateId : Nhận dạng tổng hợp
  • Dữ liệu : Sự kiện được tuần tự hóa
  • Siêu dữ liệu : Thông tin hữu ích khác từ sự kiện (ví dụ: loại sự kiện được sử dụng cho deserialize, dấu thời gian, id người khởi tạo từ lệnh, v.v.)
  • AggregateSequenceNumber : Số thứ tự trong cùng một tổng hợp (điều này rất hữu ích nếu bạn không thể ghi xảy ra không theo thứ tự, vì vậy bạn sử dụng trường này cho đồng thời lạc quan)

Tuy nhiên, nếu bạn đang tạo từ đầu, tôi khuyên bạn nên tuân theo nguyên tắc YAGNI và tạo với các trường bắt buộc tối thiểu cho trường hợp sử dụng của bạn.


Tôi sẽ tranh luận rằng BatchId có thể có khả năng liên quan đến CorrelationId và CausationId. Được sử dụng để tìm ra nguyên nhân gây ra các sự kiện và xâu chuỗi chúng lại với nhau nếu cần.
Daniel Park

Nó có thể là. Tuy nhiên, điều này là như vậy, Sẽ rất hợp lý nếu cung cấp một cách để tùy chỉnh nó (ví dụ: đặt làm id của yêu cầu), nhưng khung công tác không làm điều đó.
Fabio Marreco ngày

1

Gợi ý có thể là thiết kế theo sau là "Kích thước thay đổi từ từ" (loại = 2) sẽ giúp bạn bao gồm:

  • thứ tự các sự kiện xảy ra (thông qua khóa thay thế)
  • độ bền của mỗi trạng thái (hợp lệ từ - hợp lệ đến)

Chức năng nếp gấp trái cũng có thể triển khai được, nhưng bạn cần nghĩ đến độ phức tạp của truy vấn trong tương lai.


1

Tôi nghĩ rằng đây sẽ là một câu trả lời muộn nhưng tôi muốn chỉ ra rằng việc sử dụng RDBMS làm bộ lưu trữ nguồn cung ứng sự kiện là hoàn toàn có thể nếu yêu cầu thông lượng của bạn không cao. Tôi chỉ cho bạn xem các ví dụ về sổ cái tìm nguồn cung ứng sự kiện mà tôi xây dựng để minh họa.

https://github.com/andrewkkchan/client-ledger-service Trên đây là dịch vụ web sổ cái tìm nguồn cung ứng sự kiện. https://github.com/andrewkkchan/client-ledger-core-db Và ở trên tôi sử dụng RDBMS để tính toán các trạng thái để bạn có thể tận hưởng tất cả những lợi ích đi kèm với RDBMS như hỗ trợ giao dịch. https://github.com/andrewkkchan/client-ledger-core-memory Và tôi có một người tiêu dùng khác đang xử lý trong bộ nhớ để xử lý các vụ nổ.

Một người sẽ tranh luận rằng kho sự kiện thực tế ở trên vẫn tồn tại trong Kafka - vì RDBMS chậm cho việc chèn, đặc biệt khi việc chèn luôn nối tiếp.

Tôi hy vọng mã giúp cung cấp cho bạn một minh họa ngoài các câu trả lời lý thuyết rất tốt đã được cung cấp cho câu hỏi này.


Cảm ơn. Tôi đã xây dựng một triển khai dựa trên SQL từ lâu. Tôi không chắc tại sao RDBMS lại chậm đối với các lần chèn trừ khi bạn đã đưa ra lựa chọn không hiệu quả cho một khóa được phân cụm ở đâu đó. Chỉ nối sẽ ổn.
Neil Barnwell
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.