Menggunakan RDBMS sebagai penyimpanan sumber acara

119

Jika saya menggunakan RDBMS (misalnya SQL Server) untuk menyimpan data sumber acara, skema akan terlihat seperti apa?

Saya telah melihat beberapa variasi yang dibicarakan dalam arti abstrak, tetapi tidak ada yang konkret.

Misalnya, seseorang memiliki entitas "Produk", dan perubahan pada produk tersebut dapat berupa: Harga, Biaya, dan Deskripsi. Saya bingung apakah saya akan:

  1. Miliki tabel "ProductEvent", yang memiliki semua bidang untuk produk, di mana setiap perubahan berarti rekor baru dalam tabel itu, ditambah "siapa, apa, di mana, mengapa, kapan, dan bagaimana" (WWWWWH) yang sesuai. Ketika biaya, harga atau deskripsi berubah, baris baru ditambahkan untuk mewakili Produk.
  2. Simpan Biaya, Harga dan Deskripsi produk dalam tabel terpisah yang digabungkan ke tabel Produk dengan hubungan kunci asing. Saat perubahan pada properti tersebut terjadi, tulis baris baru dengan WWWWWH yang sesuai.
  3. Simpan WWWWWH, ditambah objek berseri yang mewakili peristiwa, dalam tabel "ProductEvent", yang berarti peristiwa itu sendiri harus dimuat, dibatalkan serialnya, dan diputar ulang dalam kode aplikasi saya untuk membangun kembali status aplikasi untuk Produk tertentu .

Terutama saya khawatir tentang opsi 2 di atas. Secara ekstrem, tabel produk akan menjadi hampir satu tabel per properti, di mana untuk memuat Status Aplikasi untuk produk tertentu akan memerlukan memuat semua peristiwa untuk produk tersebut dari setiap tabel peristiwa produk. Ledakan meja ini baunya salah bagiku.

Saya yakin "itu tergantung", dan sementara tidak ada "jawaban yang benar", saya mencoba untuk merasakan apa yang bisa diterima, dan apa yang sama sekali tidak bisa diterima. Saya juga menyadari bahwa NoSQL dapat membantu di sini, di mana peristiwa dapat disimpan terhadap akar agregat, yang berarti hanya satu permintaan ke database untuk mendapatkan peristiwa untuk membangun kembali objek dari, tetapi kami tidak menggunakan db NoSQL di saat jadi saya mencari alternatif.

Neil Barnwell
sumber
2
Dalam bentuknya yang paling sederhana: [Event] {AggregateId, AggregateVersion, EventPayload}. Tidak perlu untuk jenis agregat, tetapi Anda BISA menyimpannya secara opsional. Tidak perlu jenis acara, tetapi Anda BISA menyimpannya secara opsional. Ini adalah daftar panjang hal-hal yang telah terjadi, yang lainnya hanyalah pengoptimalan.
Yves Reynhout
7
Pasti menjauh dari # 1 dan # 2. Susun semuanya menjadi gumpalan dan simpan seperti itu.
Jonathan Oliver

Jawaban:

109

Toko acara tidak perlu tahu tentang bidang atau properti acara tertentu. Jika tidak, setiap modifikasi model Anda akan mengakibatkan keharusan untuk memigrasi database Anda (seperti halnya persistensi kuno berbasis negara). Oleh karena itu saya tidak akan merekomendasikan opsi 1 dan 2 sama sekali.

Di bawah ini adalah skema yang digunakan di Ncqrs . Seperti yang Anda lihat, tabel "Events" menyimpan data terkait sebagai CLOB (mis. JSON atau XML). Ini sesuai dengan opsi 3 Anda (Hanya saja tidak ada tabel "ProductEvents" karena Anda hanya memerlukan satu tabel "Peristiwa" generik. Di Ncqrs, pemetaan ke Akar Gabungan Anda terjadi melalui tabel "Sumber Peristiwa", di mana setiap Sumber Peristiwa sesuai dengan aktual Akar Agregat.)

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

Mekanisme persistensi SQL dari implementasi Toko Peristiwa Jonathan Oliver pada dasarnya terdiri dari satu tabel yang disebut "Komit" dengan bidang BLOB "Muatan". Ini hampir sama seperti di Ncqrs, hanya saja properti acara itu berseri dalam format biner (yang, misalnya, menambahkan dukungan enkripsi).

Greg Young merekomendasikan pendekatan serupa, seperti yang didokumentasikan secara luas di situs web Greg .

Skema tabel "Peristiwa" prototipikal miliknya berbunyi:

Table Events
    AggregateId [Guid],
    Data [Blob],
    SequenceNumber [Long],
    Version [Int]
Dennis Traub
sumber
9
Jawaban bagus! Salah satu argumen utama yang terus saya baca tentang penggunaan EventSourcing adalah kemampuan untuk menanyakan riwayat. Bagaimana cara membuat alat pelaporan yang efisien dalam melakukan kueri ketika semua data yang menarik diserialkan sebagai XML atau JSON? Apakah ada artikel menarik yang mencari solusi berbasis tabel?
Marijn Huizendveld
11
@MarijnHuizendveld Anda mungkin tidak ingin membuat kueri terhadap penyimpanan acara itu sendiri. Solusi paling umum adalah menghubungkan beberapa penangan kejadian yang memproyeksikan kejadian ke dalam database pelaporan atau BI. Pemutaran ulang sejarah acara melawan penangan ini.
Dennis Traub
1
@Denis Traub terima kasih atas jawaban Anda. Mengapa tidak mengajukan kueri terhadap toko acara itu sendiri? Saya khawatir ini akan menjadi sangat berantakan / intens jika kami harus memutar ulang seluruh riwayat setiap kali kami menemukan kasus BI baru?
Marijn Huizendveld
1
Saya pikir pada titik tertentu Anda seharusnya juga memiliki tabel selain penyimpanan acara, untuk menyimpan data dari model dalam keadaan terbaru? Dan Anda membagi model menjadi model baca dan model tulis. Model tulis bertentangan dengan toko acara, dan toko acara memperbarui bela diri untuk model baca. Model baca berisi tabel yang mewakili entitas di sistem Anda - sehingga Anda dapat menggunakan model baca untuk melakukan pelaporan dan melihat. Saya pasti salah paham tentang sesuatu.
theBoringCoder
10
@theBoringCoder Sepertinya Anda memiliki Event Sourcing dan CQRS yang bingung atau setidaknya ada di kepala Anda. Mereka sering ditemukan bersama tetapi mereka bukanlah hal yang sama. CQRS meminta Anda memisahkan model baca dan tulis sementara Sumber Acara meminta Anda menggunakan aliran acara sebagai satu-satunya sumber kebenaran dalam aplikasi Anda.
Bryan Anderson
7

Proyek GitHub CQRS.NET memiliki beberapa contoh konkret tentang bagaimana Anda dapat melakukan EventStores dalam beberapa teknologi berbeda. Pada saat penulisan, ada implementasi dalam SQL menggunakan Linq2SQL dan skema SQL yang menyertainya, ada satu untuk MongoDB , satu untuk DocumentDB (CosmosDB jika Anda berada di Azure) dan satu lagi menggunakan EventStore (seperti yang disebutkan di atas). Ada lebih banyak di Azure seperti Penyimpanan Tabel dan penyimpanan Blob yang sangat mirip dengan penyimpanan file datar.

Saya kira poin utama di sini adalah bahwa mereka semua sesuai dengan prinsip / kontrak yang sama. Mereka semua menyimpan informasi di satu tempat / wadah / tabel, mereka menggunakan metadata untuk mengidentifikasi satu peristiwa dari yang lain dan 'hanya' menyimpan seluruh peristiwa sebagaimana adanya - dalam beberapa kasus berseri, dalam teknologi pendukung, sebagaimana adanya. Jadi, bergantung pada apakah Anda memilih database dokumen, database relasional, atau bahkan file datar, ada beberapa cara berbeda untuk semuanya mencapai maksud yang sama dari penyimpanan acara (ini berguna jika Anda berubah pikiran kapan saja dan merasa perlu bermigrasi atau mendukung lebih dari satu teknologi penyimpanan).

Sebagai pengembang di proyek ini, saya dapat membagikan beberapa wawasan tentang beberapa pilihan yang kami buat.

Pertama-tama kami menemukan (bahkan dengan UUID / GUID yang unik alih-alih bilangan bulat) karena berbagai alasan ID berurutan terjadi karena alasan strategis, jadi hanya memiliki ID tidak cukup unik untuk sebuah kunci, jadi kami menggabungkan kolom kunci ID utama kami dengan data / tipe objek untuk membuat kunci yang benar-benar unik (dalam arti aplikasi Anda). Saya tahu beberapa orang mengatakan Anda tidak perlu menyimpannya, tetapi itu akan tergantung pada apakah Anda greenfield atau harus hidup berdampingan dengan sistem yang ada.

Kami terjebak dengan satu wadah / tabel / koleksi untuk alasan pemeliharaan, tetapi kami bermain-main dengan tabel terpisah per entitas / objek. Dalam praktiknya, kami menemukan bahwa aplikasi tersebut memerlukan izin "BUAT" (yang secara umum bukanlah ide yang baik ... umumnya, selalu ada pengecualian / pengecualian) atau setiap kali entitas / objek baru muncul atau diterapkan, baru wadah penyimpanan / meja / koleksi perlu dibuat. Kami mendapati bahwa ini sangat lambat untuk pengembangan lokal dan bermasalah untuk penerapan produksi. Anda mungkin tidak, tapi itulah pengalaman dunia nyata kami.

Hal lain yang perlu diingat adalah bahwa meminta tindakan X terjadi dapat mengakibatkan banyak peristiwa berbeda terjadi, sehingga mengetahui semua peristiwa yang dihasilkan oleh perintah / peristiwa / apa pun itu berguna. Mereka mungkin juga berada di berbagai jenis objek yang berbeda misalnya mendorong "beli" di keranjang belanja dapat memicu peristiwa akun dan pergudangan untuk diaktifkan. Aplikasi yang memakan mungkin ingin mengetahui semua ini, jadi kami menambahkan CorrelationId. Ini berarti konsumen dapat meminta semua peristiwa yang diangkat sebagai hasil dari permintaan mereka. Anda akan melihatnya di skema .

Khususnya dengan SQL, kami menemukan bahwa kinerja benar-benar menjadi penghambat jika indeks dan partisi tidak digunakan secara memadai. Ingat acara perlu dialirkan dalam urutan terbalik jika Anda menggunakan snapshot. Kami mencoba beberapa indeks berbeda dan menemukan bahwa dalam praktiknya, beberapa indeks tambahan diperlukan untuk men-debug aplikasi dunia nyata dalam produksi. Sekali lagi Anda akan melihat itu di skema .

Metadata dalam produksi lainnya berguna selama investigasi berbasis produksi, stempel waktu memberi kami wawasan tentang urutan peristiwa yang dipertahankan vs dimunculkan. Itu memberi kami beberapa bantuan pada sistem yang sangat didorong oleh peristiwa yang mengangkat sejumlah besar peristiwa, memberi kami informasi tentang kinerja hal-hal seperti jaringan dan distribusi sistem di seluruh jaringan.

cdmdotnet.dll
sumber
Bagus sekali, terima kasih. Kebetulan, lama sejak menulis pertanyaan ini, saya telah membuat beberapa sendiri sebagai bagian dari perpustakaan Inforigami.Regalo saya di github. RavenDB, SQL Server dan implementasi EventStore. Ingin tahu tentang melakukan yang berbasis file, untuk tertawa. :)
Neil Barnwell
1
Bersulang. Saya menambahkan jawabannya terutama untuk orang lain yang baru-baru ini menemukannya dan berbagi beberapa pelajaran yang dipetik, bukan hanya hasilnya.
cdmdotnet
3

Anda mungkin ingin melihat Datomic.

Datomic adalah basis data fleksibel, fakta berbasis waktu , kueri pendukung dan penggabungan, dengan skalabilitas elastis, dan transaksi ACID.

Saya menulis jawaban rinci di sini

Anda dapat menonton ceramah dari Stuart Halloway yang menjelaskan desain Datomic di sini

Karena Datomic menyimpan fakta tepat waktu, Anda dapat menggunakannya untuk kasus penggunaan sumber acara, dan banyak lagi.

kisai
sumber
2

Saya pikir solusi (1 & 2) dapat menjadi masalah dengan sangat cepat seiring dengan berkembangnya model domain Anda. Bidang baru dibuat, beberapa mengubah arti, dan beberapa tidak dapat digunakan lagi. Pada akhirnya tabel Anda akan memiliki lusinan bidang nullable, dan pemuatan acara akan berantakan.

Selain itu, ingatlah bahwa penyimpanan peristiwa harus digunakan hanya untuk menulis, Anda hanya mengkueri untuk memuat peristiwa, bukan properti agregat. Mereka adalah hal yang terpisah (itulah inti dari CQRS).

Solusi 3 apa yang biasa dilakukan orang, ada banyak cara untuk mencapainya.

Sebagai contoh, EventFlow CQRS saat digunakan dengan SQL Server membuat tabel dengan skema ini:

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
)

dimana:

  • GlobalSequenceNumber : Identifikasi global sederhana, dapat digunakan untuk memesan atau mengidentifikasi peristiwa yang hilang saat Anda membuat proyeksi (readmodel).
  • BatchId : Identifikasi grup peristiwa yang dimasukkan secara atomik (TBH, tidak tahu mengapa ini berguna)
  • AggregateId : Identifikasi agregat
  • Data : Acara berseri
  • Metadata : Informasi berguna lainnya dari acara (mis. Jenis acara yang digunakan untuk deserialize, timestamp, id originator dari perintah, dll.)
  • AggregateSequenceNumber : Nomor urutan dalam agregat yang sama (ini berguna jika Anda tidak dapat membuat penulisan yang tidak berurutan, jadi Anda menggunakan kolom ini untuk untuk konkurensi yang optimis)

Namun, jika Anda membuat dari awal, saya akan merekomendasikan mengikuti prinsip YAGNI, dan membuat dengan bidang yang diperlukan minimal untuk kasus penggunaan Anda.

Fabio Marreco
sumber
Saya berpendapat bahwa BatchId mungkin berpotensi terkait dengan CorrelationId dan CausationId. Digunakan untuk mencari tahu apa yang menyebabkan peristiwa, dan merangkainya jika perlu.
Daniel Park
Bisa jadi. Namun demikian, akan masuk akal untuk menyediakan cara untuk menyesuaikannya (misalnya menyetel sebagai id permintaan), tetapi framework tidak melakukan itu.
Fabio Marreco
1

Petunjuk yang mungkin adalah desain diikuti dengan "Dimensi yang Berubah Perlahan" (type = 2) akan membantu Anda untuk membahas:

  • urutan kejadian yang terjadi (melalui kunci pengganti)
  • daya tahan setiap negara (berlaku dari - berlaku hingga)

Fungsi lipatan kiri juga baik-baik saja untuk diterapkan, tetapi Anda perlu memikirkan kerumitan kueri di masa mendatang.

Viktor Nakonechnyy
sumber
1

Saya rasa ini akan menjadi jawaban yang terlambat tetapi saya ingin menunjukkan bahwa menggunakan RDBMS sebagai penyimpanan sumber acara benar-benar mungkin jika persyaratan throughput Anda tidak tinggi. Saya hanya akan menunjukkan contoh buku besar sumber acara yang saya buat untuk diilustrasikan.

https://github.com/andrewkkchan/client-ledger-service Di atas adalah layanan web buku besar sumber acara. https://github.com/andrewkkchan/client-ledger-core-db Dan di atas saya menggunakan RDBMS untuk menghitung status sehingga Anda dapat menikmati semua keuntungan yang datang dengan RDBMS seperti dukungan transaksi. https://github.com/andrewkkchan/client-ledger-core-memory Dan saya memiliki konsumen lain untuk diproses dalam memori untuk menangani semburan.

Orang akan berpendapat bahwa penyimpanan acara sebenarnya di atas masih ada di Kafka-- karena RDBMS lambat untuk dimasukkan terutama ketika penyisipan selalu ditambahkan.

Saya berharap kode membantu memberi Anda ilustrasi selain dari jawaban teoritis yang sangat baik yang sudah disediakan untuk pertanyaan ini.

Andrew Chan
sumber
Terima kasih. Saya sudah lama membangun implementasi berbasis SQL. Saya tidak yakin mengapa RDBMS lambat untuk disisipkan kecuali Anda telah membuat pilihan yang tidak efisien untuk kunci berkerumun di suatu tempat. Tambahkan saja seharusnya baik-baik saja.
Neil Barnwell