Merehidrasi Agregat dari proyeksi "snapshots" daripada Event Store

14

Jadi saya sudah lama bermain-main dengan Event Sourcing dan CQRS, meskipun saya tidak pernah memiliki kesempatan untuk menerapkan pola pada proyek nyata.

Saya memahami manfaat memisahkan masalah baca dan tulis Anda, dan saya menghargai bagaimana Event Sourcing memudahkan untuk memproyeksikan perubahan status ke database "Baca Model" yang berbeda dari Toko Acara Anda.

Yang tidak saya ketahui dengan jelas adalah mengapa Anda akan merehidrasi Agregat Anda dari Event Store itu sendiri.

Jika memproyeksikan perubahan pada basis data "baca" begitu mudah, mengapa tidak selalu memproyeksikan perubahan pada basis data "tulis" yang skemanya sangat cocok dengan model domain Anda? Ini secara efektif akan menjadi basis data snapshot.

Saya membayangkan ini pasti sangat umum di aplikasi ES + CQRS di alam liar.

Jika ini masalahnya, apakah Event Store hanya berguna ketika membangun kembali database "write" Anda sebagai akibat dari perubahan skema? Atau apakah saya melewatkan sesuatu yang lebih besar?

MetaFight
sumber
Tidak ada yang salah dengan menulis asinkron ke toko model tulis dan menggunakannya secara eksklusif untuk memuat entitas. Masalah konsistensi yang sama persis hadir apakah Anda melakukannya atau tidak. Kunci untuk memperbaiki masalah konsistensi tersebut adalah dengan memodelkan entitas Anda secara berbeda. Tidak ada keajaiban tentang Pengadaan Acara yang menyelesaikan masalah konsistensi tersebut. Keajaiban terletak pada pemodelan dan tidak peduli. Ada aplikasi spesifik yang memang memerlukan konsistensi pada tingkat itu yang memiliki entitas yang sangat kontroversial tidak peduli bagaimana Anda memodelkan mereka dan akan memerlukan perhatian khusus.
Andrew Larsson
Selama Anda bisa menjamin pengiriman acara. Untuk melakukan ini, aplikasi Anda hanya perlu secara sinkron mempublikasikan suatu peristiwa ke dalam bus acara yang tahan lama. Setelah penerbitan, pekerjaan aplikasi selesai. Bus kemudian akan mengirimkannya ke berbagai penangan acara: satu untuk memperbarui toko peristiwa, satu untuk memperbarui toko negara, dan yang lainnya diperlukan untuk memperbarui toko yang sudah dibaca. Alasan Anda menggunakan Pengadaan Acara adalah karena Anda tidak lagi peduli dengan konsistensi langsung. Rangkullah itu.
Andrew Larsson
Tidak ada alasan Anda harus terus memuat entitas Anda dari toko acara. Bukan itu tujuannya. Tujuannya adalah untuk menyediakan buku besar baku dan permanen tentang segala sesuatu yang telah terjadi dalam sistem. Toko entitas negara dan model baca terdenormalisasi adalah untuk memuat dan membaca.
Andrew Larsson

Jawaban:

14

Yang tidak saya ketahui dengan jelas adalah mengapa Anda akan merehidrasi Agregat Anda dari Event Store itu sendiri.

Karena "peristiwa" adalah buku catatan.

Jika memproyeksikan perubahan pada basis data "baca" begitu mudah, mengapa tidak selalu memproyeksikan perubahan pada basis data "tulis" yang skemanya sangat cocok dengan model domain Anda? Ini secara efektif akan menjadi basis data snapshot.

Iya; kadang-kadang optimasi kinerja yang berguna untuk menggunakan salinan cache dari status agregat, daripada membuat ulang keadaan itu dari awal setiap waktu. Ingat: aturan pertama pengoptimalan kinerja adalah "Jangan". Ini menambah kompleksitas ekstra untuk solusi, dan Anda lebih suka menghindarinya sampai Anda memiliki motivasi bisnis yang meyakinkan.

Jika ini masalahnya, apakah Event Store hanya berguna ketika membangun kembali database "write" Anda sebagai akibat dari perubahan skema? Atau apakah saya melewatkan sesuatu yang lebih besar?

Anda melewatkan sesuatu yang lebih besar.

Poin pertama adalah bahwa jika Anda mempertimbangkan solusi yang berasal dari suatu peristiwa, itu karena Anda berharap akan ada nilai dalam melestarikan sejarah tentang apa yang telah terjadi, yang artinya Anda ingin membuat perubahan yang tidak merusak .

Jadi itu sebabnya kami menulis ke toko acara sama sekali.

Secara khusus, ini berarti bahwa setiap perubahan harus ditulis ke toko acara.

Penulis yang bersaing dapat berpotensi memusnahkan tulisan masing-masing, atau mendorong sistem ke kondisi yang tidak diinginkan, jika mereka tidak mengetahui adanya suntingan satu sama lain. Jadi pendekatan yang biasa digunakan ketika Anda membutuhkan konsistensi adalah menulis tulisan Anda pada posisi tertentu dalam jurnal (analog dengan PUT bersyarat dalam HTTP api). Tulisan yang gagal memberi tahu penulis bahwa pemahaman mereka tentang jurnal saat ini tidak sinkron, dan bahwa mereka harus pulih.

Kembali ke posisi yang dikenal baik kemudian memutar ulang peristiwa tambahan karena titik itu adalah strategi pemulihan umum. Posisi bagus yang diketahui itu bisa berupa salinan dari apa yang ada di cache lokal, atau representasi di toko snapshot Anda.

Di jalur bahagia, Anda dapat menyimpan snapshot agregat di memori; Anda hanya perlu menjangkau toko eksternal ketika tidak ada salinan lokal yang tersedia.

Selain itu, Anda tidak perlu sepenuhnya terperangkap, jika Anda memiliki akses ke buku catatan.

Jadi pendekatan yang biasa ( jika menggunakan repositori snapshot) adalah untuk mempertahankannya secara tidak sinkron . Dengan begitu, jika Anda perlu memulihkan, Anda dapat melakukannya tanpa memuat ulang dan memutar ulang seluruh riwayat agregat.

Ada banyak kasus di mana kompleksitas ini tidak menarik, karena agregat berbutir halus dengan masa hidup yang dicakup biasanya tidak mengumpulkan cukup peristiwa untuk manfaat melebihi biaya mempertahankan cache snapshot.

Tetapi ketika itu adalah alat yang tepat untuk masalah tersebut, memuat representasi basi dari agregat ke dalam model tulis, kemudian memperbaruinya dengan acara tambahan, adalah hal yang sangat masuk akal untuk dilakukan.

VoiceOfUnreason
sumber
Ini semua terdengar masuk akal. Ketika saya bermain-main dengan implementasi dummy ES beberapa waktu yang lalu saya menggunakan EventStore untuk menjamin konsistensi tetapi saya juga secara sinkron menulis apa yang Anda sebut "repositori snapshot". Ini berarti keadaan saat ini selalu siap dibaca tanpa harus memutar ulang acara. Saya curiga ini tidak akan bagus, tetapi karena itu hanya latihan, saya tidak keberatan.
MetaFight
6

Karena Anda tidak menentukan apa yang akan menjadi tujuan dari database "tulis", saya akan berasumsi di sini bahwa yang Anda maksudkan adalah ini: ketika mendaftarkan pembaruan baru ke agregat, alih-alih membangun kembali agregat dari toko kejadian, Anda angkat dari database "tulis", validasi perubahan, dan keluarkan suatu peristiwa.

Jika ini yang Anda maksud, maka strategi ini akan menciptakan kondisi ketidakkonsistenan: jika pembaruan baru terjadi sebelum yang terakhir memiliki kesempatan untuk membuatnya menjadi database "tulis", pembaruan baru akan berakhir divalidasi terhadap data yang sudah usang, dengan demikian berpotensi mengeluarkan peristiwa "tidak mungkin" (yaitu "tidak diizinkan") dan merusak kondisi sistem.

Misalnya, perhatikan contoh berdiri pemesanan kursi di teater. Untuk mencegah pemesanan ganda, Anda perlu memastikan bahwa kursi yang dipesan belum diambil - inilah yang Anda sebut "validasi". Untuk melakukan itu, Anda menyimpan daftar kursi yang sudah dipesan di database "tulis". Kemudian, ketika permintaan pemesanan masuk, Anda memeriksa apakah kursi yang diminta ada dalam daftar, dan jika tidak, berikan acara "dipesan", jika tidak balas dengan pesan kesalahan. Kemudian Anda menjalankan proses proyeksi, di mana Anda mendengarkan acara "dipesan" dan menambahkan kursi dipesan ke daftar di database "tulis".

Biasanya, sistem akan berfungsi seperti ini:

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Projection process catches the event, adds seat #1 to the "already booked" list.
5. Another request to book seat #1.
6. Check in the list: the list contains seat #1
7. Respond with an error message.

Namun, bagaimana jika permintaan masuk terlalu cepat, dan langkah 5 terjadi sebelum langkah 4?

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Another request to book seat #1.
5. Check in the list: the list is still empty.
6. Issue another "booked seat #1" event.

Sekarang Anda memiliki dua acara untuk memesan kursi yang sama. Status sistem rusak.

Untuk mencegah hal ini terjadi, Anda tidak boleh memvalidasi pembaruan terhadap suatu proyeksi. Untuk memvalidasi pembaruan, Anda membangun kembali agregat dari toko acara, lalu memvalidasi pembaruan yang menentangnya. Setelah itu, Anda mengeluarkan acara, tetapi gunakan penjaga stempel waktu untuk memastikan bahwa tidak ada acara baru yang dikeluarkan sejak Anda terakhir membaca dari toko. Jika gagal, Anda hanya perlu mencoba lagi.

Membangun kembali agregat dari toko acara mungkin membawa penalti kinerja. Untuk mengurangi ini, Anda dapat menyimpan snapshot agregat tepat di aliran acara, ditandai dengan ID peristiwa dari mana snapshot dibuat. Dengan cara ini, Anda dapat membangun kembali agregat dengan memuat snapshot terbaru dan hanya mengulang peristiwa yang datang setelahnya, sebagai lawan untuk selalu memutar ulang seluruh aliran peristiwa dari awal waktu.

Fyodor Soikin
sumber
Terima kasih atas jawaban Anda (dan maaf telah mengambil begitu lama untuk merespons). Apa yang Anda katakan tentang validasi terhadap database tulis belum tentu benar. Seperti yang saya sebutkan di komentar lain, dalam contoh implementasi ES saya bermain dengan saya memastikan untuk memperbarui database tulis saya secara sinkron (dan menyimpan concurrencyId / timestamp). Ini memungkinkan saya untuk mendeteksi pelanggaran konkurensi optimis tanpa harus siap dari EventStore. Memang, menulis sinkron saja tidak melindungi terhadap korupsi data, tetapi saya juga melakukan menulis akses tunggal (single-threaded).
MetaFight
Jadi, masalah konsistensi saya sudah beres. Padahal, saya menganggap ini dengan mengorbankan skalabilitas.
MetaFight
Sinkronisasi menulis ke database tulis masih membawa bahaya korupsi: apa yang terjadi jika penulisan Anda ke toko acara berhasil, tetapi penulisan Anda ke database tulis gagal?
Fyodor Soikin
1
Jika proyeksi baca gagal, itu hanya akan mencoba lagi sampai berhasil. Jika crash langsung, ia akan bangun dan melanjutkan dari tempat crash - dengan kata lain, coba lagi. Efek luar yang bisa diamati tidak akan berbeda dari itu hanya berjalan agak lambat. Jika proyeksi terus gagal dan gagal secara konsisten, itu berarti ada bug di dalamnya, dan itu harus diperbaiki. Setelah diperbaiki, itu akan mulai berjalan dari kondisi baik terakhir. Jika seluruh basis data baca menjadi rusak karena bug, saya hanya akan membangun kembali basis data dari awal menggunakan riwayat acara.
Fyodor Soikin
1
Tidak ada data yang hilang, itulah intinya. Data dapat macet dalam bentuk yang tidak nyaman (untuk membaca) untuk sementara waktu, tetapi tidak pernah hilang.
Fyodor Soikin
3

Alasan utamanya adalah kinerja. Anda dapat menyimpan snapshot untuk setiap komit (komit = peristiwa yang dihasilkan oleh satu perintah, biasanya hanya satu peristiwa) tetapi ini mahal. Sepanjang snapshot Anda perlu menyimpan juga komit, jika tidak, itu bukan sumber acara. Dan semua ini harus dilakukan secara atomis, semuanya atau tidak sama sekali. Pertanyaan Anda hanya valid jika basis data / tabel / koleksi terpisah digunakan (jika tidak akan menjadi sumber acara) sehingga Anda terpaksa menggunakan transaksi untuk menjamin konsistensi. Transaksi tidak dapat diskalakan. Aliran acara yang hanya ditambahkan (toko acara) adalah ibu dari skalabilitas.

Alasan kedua adalah enkapsulasi Agregat. Anda harus melindunginya. Ini berarti bahwa Agregat harus bebas untuk mengubah perwakilan internalnya kapan saja. Jika Anda menyimpannya dan sangat bergantung padanya maka Anda akan mengalami kesulitan dengan versi, yang pasti akan terjadi. Dalam situasi ketika Anda menggunakan snapshot hanya sebagai optimisasi, ketika skema mengubah Anda cukup mengabaikan snapshot itu ( hanya ? Saya tidak benar-benar berpikir begitu; semoga berhasil menentukan bahwa skema Aggregate berubah - termasuk semua entitas bersarang dan objek nilai - dalam cara efisien dan mengelola itu).

Constantin Galbenu
sumber
Ketika skema Agregat saya berubah, bukankah itu hanya masalah memutar ulang acara saya untuk menghasilkan database "tulis" yang diperbarui?
MetaFight
Masalahnya mendeteksi perubahan itu. Agregat bisa sangat besar, dengan banyak file / kelas.
Constantin Galbenu
Saya tidak mengerti. Perubahan akan terjadi dengan rilis perangkat lunak. Rilis ini mungkin akan datang dengan skrip database untuk membuat ulang database "write".
MetaFight
Banyak pekerjaan yang harus dilakukan untuk skrip migrasi. Saat dijalankan aplikasi harus turun.
Constantin Galbenu
@MetaFight jika alirannya sangat besar akan membutuhkan banyak waktu untuk membangun kembali skema agregat baru ... Saya sedang memikirkan snapshot yang merupakan keadaan proyeksi langsung yang dapat dijalankan sebelum rilis agregat baru skema
Narvalex