Sumber acara, satu peristiwa, keadaan dua agregat berubah

10

Saya mencoba mempelajari cara-cara DDD dan mata pelajaran terkait. Saya datang dengan ide konteks terbatas sederhana untuk menerapkan "bank": ada akun, uang dapat disimpan, ditarik dan ditransfer di antara mereka. Penting juga untuk menyimpan sejarah perubahan.

Saya mengidentifikasi entitas Akun dan sumber acara akan lebih baik untuk melacak perubahan di dalamnya. Entitas atau objek nilai lain tidak relevan dengan masalah, jadi saya tidak akan menyebutkannya.

Ketika mempertimbangkan setoran dan penarikan - ini relatif sederhana, karena hanya ada satu agregat yang dimodifikasi.

Ketika mentransfernya berbeda - dua agregat harus dimodifikasi oleh satu peristiwa MoneyTransferred . DDD tidak lagi memodifikasi beberapa agregat dalam satu transaksi. Di sisi lain aturan sumber acara adalah untuk menerapkan acara ke entitas dan memodifikasi negara berdasarkan mereka. Jika acara dapat disimpan hanya dalam database, tidak akan ada masalah. Tetapi untuk mencegah modifikasi bersamaan dari event yang bersumber dari entitas, kita harus mengimplementasikan sesuatu yang mengubah aliran acara dari setiap agregat (untuk menjaga batas transaksi mereka). Dengan versi muncul masalah lain - saya tidak bisa menggunakan struktur sederhana untuk menyimpan acara dan membacanya kembali untuk menerapkannya ke agregat.

Pertanyaan saya adalah - bagaimana saya bisa menggabungkan ketiga prinsip: "satu agregat satu transaksi", "peristiwa-> perubahan agregat" dan "pencegahan modifikasi bersamaan"?

cocsackie
sumber

Jawaban:

7

Ketika mentransfernya berbeda - dua agregat harus dimodifikasi oleh satu peristiwa MoneyTransferred.

Mentransfer uang adalah tindakan terpisah dari memperbarui buku besar.

MoneyTransferred
AccountCredited
AccountDebited

Latihan yang akhirnya membuat saya bingung adalah menyadari bahwa itu AccountOverdrawnadalah suatu peristiwa, itu menggambarkan keadaan akun tanpa memperhatikan peserta lain dalam pertukaran ini, jadi harus ada perintah yang dijalankan terhadap akun yang memproduksinya.

Anda tidak dapat memperoleh keadaan yang wajar seperti AccountOverdrawndari model baca, karena Anda tidak mungkin tahu apakah Anda telah melihat semua peristiwa - hanya agregat itu sendiri yang memiliki pandangan penuh tentang sejarah pada saat tertentu.

Jawabannya, tentu saja, ada di sana dalam bahasa mana-mana - akun dikreditkan atau didebitkan untuk mencerminkan kewajiban bank kepada pelanggannya.

Baiklah, tapi itu berarti saya harus menggunakan acara AccountCredited dan AccountDebited untuk setoran dan penarikan juga, jadi saya hanya mendaftar bukan penyebab perubahan, tetapi perubahan yang disebabkan oleh beberapa tindakan lain. Jika saya ingin membalikkan tindakan saya tidak bisa, karena tidak semua acara terdaftar.

Saya tidak sepenuhnya yakin yang mengikuti, karena Anda memang memiliki (untuk kasus seperti ini) pengidentifikasi korelasi alami, yang merupakan id transaksi itu sendiri.

Hal kedua - itu berarti saya harus menggunakan sesuatu seperti saga.

Ejaan yang sedikit berbeda: Anda membutuhkan sesuatu seperti manusia yang mengirimkan perintah yang tepat .

Setidaknya ada dua cara Anda bisa melakukannya. Salah satunya adalah meminta pelanggan mendengarkan MoneyTransferred, dan mengirimkan dua perintah ke buku besar.

Alternatif lain adalah melacak pemrosesan transaksi sebagai agregat terpisah - anggap saja sebagai daftar periksa semua hal yang perlu dilakukan sejak transaksi terjadi. Jadi MoneyTransferredpengendali acara mengirimkan ProcessTransaction, yang menjadwalkan pekerjaan yang harus dilakukan dan memeriksa pekerjaan apa yang telah selesai.

VoiceOfUnreason
sumber
Baiklah, tapi itu berarti saya harus menggunakan acara AccountCredited dan AccountDebited untuk setoran dan penarikan juga, jadi saya hanya mendaftar bukan penyebab perubahan, tetapi perubahan yang disebabkan oleh beberapa tindakan lain. Jika saya ingin membalikkan tindakan saya tidak bisa, karena tidak semua acara terdaftar. Bagaimana saya bisa melakukan ini (kausalitas peristiwa)? Hal kedua - itu berarti saya harus menggunakan sesuatu seperti saga. Bagaimana seharusnya transfer dimodelkan? Suatu saat saya memiliki metode transfer pada akun. Ketika dipanggil menerbitkan acara MoneyTransferred . Saya tidak tahu apa yang harus memulai sesuatu seperti saga.
cocsackie
Bukankah -> AccountCredited dan AccoundDebited lalu MoneyTransferred ? Solusi pertama memperbarui kedua agregat dalam satu transaksi (tidak ada jaminan konsistensi dalam bentuk apa pun )? Juga tidak ada agregat yang dapat mempublikasikan MoneyTransferred -> tidak ada korelasi. Solusi kedua tampaknya lebih baik - ProcessTransaction dapat mempublikasikan MoneyTransferred dan untuk menghindari beberapa modifikasi agregat dalam satu transaksi saya dapat mempublikasikan acara dari Akun setelah melakukan transaksi. Maaf karena rewel. Sulit dimengerti untuk pemula - tidak bisa hanya menggunakan satu pola tanpa pola.
cocsackie
1

Detail penting dalam memahami akun berbasis transaksi: balanceatribut accountsebenarnya adalah contoh dari denormalisasi. Itu ada untuk kenyamanan. Pada kenyataannya, saldo akun adalah jumlah transaksi, dan Anda tidak benar-benar membutuhkan akun itu sendiri untuk memiliki saldo.

Mengingat hal ini, tindakan mentransfer uang tidak harus memperbarui accounttetapi untuk memasukkan transaction.

Yang sedang berkata, ada aturan penting lainnya: tindakan menambahkan transactionharus atomik dengan pembaruan ke (bidang keseimbangan dinormalisasi) account.

Sekarang jika saya memahami konsep agregat DDD, berikut ini sepertinya relevan:

Agregat adalah batas logis untuk hal-hal yang dapat berubah dalam transaksi bisnis konteks tertentu. Agregat dapat diwakili oleh satu kelas atau banyak kelas. Jika lebih dari satu kelas merupakan agregat maka salah satunya adalah kelas atau entitas root. Semua akses ke agregat dari luar harus terjadi melalui kelas root.

Jadi dalam hal desain DDD saya sarankan:

  1. Ada satu agregat untuk mewakili transfer

  2. Agregat terdiri dari objek-objek berikut: transfer (objek root); objek root ditautkan ke dua daftar transaksi (satu untuk setiap akun); dan setiap daftar transaksi ditautkan ke satu akun.

  3. Semua akses ke transfer harus direnungkan oleh objek root (yang transfer).

Jika Anda mencoba menerapkan dukungan transfer asinkron, maka kode utama Anda hanya perlu khawatir tentang membuat transfer, dalam status "menunggu". Anda mungkin memiliki utas lain atau pekerjaan yang benar-benar memindahkan uang (memasukkan ke dalam riwayat transaksi, dan karenanya memperbarui saldo) dan menetapkan transfer ke "diposkan."

Jika Anda mencari untuk menerapkan transaksi transfer real-time, memblokir, maka logika bisnis harus membuat transferdan objek yang akan mengkoordinasikan kegiatan lain secara real time.

Dalam hal mencegah masalah konkurensi, urutan pertama bisnis harus memasukkan transaksi debit ke daftar transaksi untuk akun sumber (tentu saja memperbarui saldo). Ini harus dilakukan secara atom pada tingkat basis data (melalui prosedur tersimpan). Setelah debit terjadi, sisa transfer harus dapat berhasil terlepas dari masalah konkurensi, karena seharusnya tidak ada aturan bisnis yang mencegah kredit ke akun target.

(Di dunia nyata, rekening bank memiliki konsep memo pos yang mendukung konsep komit dua fase yang malas. Pembuatan memo pos itu ringan dan mudah, dan juga dapat diputar kembali tanpa masalah. Konversi dari posting memo ke pos sulit adalah ketika uang benar-benar bergerak - ini tidak dapat dibatalkan - dan merupakan fase kedua dari komitmen dua fase, terjadi hanya setelah semua aturan validasi diperiksa).

John Wu
sumber
0

Saya juga sedang dalam tahap pembelajaran. Dari sudut pandang implementasi, ini adalah bagaimana saya merasa Anda akan melakukan tindakan ini.

Kirim TransferMoneyCommand yang Meningkatkan acara berikut [MoneyTransferEvent, AccountDebitedEvent]

Perhatikan sebelum memunculkan peristiwa ini, validasi perintah dangkal dan validasi logika domain perlu dilakukan, yaitu apakah akun memiliki cukup saldo?

Tetap ada acara (dengan versi) untuk memastikan tidak ada masalah konsistensi. Perhatikan bahwa mungkin ada perintah konkuren lainnya (seperti menarik semua uang) yang berhasil dan menyelamatkan acara sebelum ini, sehingga keadaan agregat saat ini mungkin kedaluwarsa dan oleh karena itu peristiwa tersebut dibesarkan pada keadaan lama dan tidak benar. Jika penyimpanan acara gagal, Anda harus mencoba kembali perintah dari awal.

Setelah acara berhasil disimpan dalam database Anda dapat mempublikasikan dua peristiwa yang dimunculkan.

AccountDebitedEvent akan menghapus uang dari akun pembayar (memperbarui negara agregat dan model tampilan / proyeksi terkait)

MoneyTransferEvent memulai Saga / Process Manager.

Tugas saga / manajer proses adalah mencoba untuk mengkredit rekening penerima pembayaran, jika gagal, perlu mengkredit saldo kembali ke pembayar.

Saga / Manajer proses akan menerbitkan CreditAccountCommand yang diterapkan ke akun penerima pembayaran dan jika berhasil daripada AccountCreditedEvent akan dinaikkan.

Dari sudut pandang sumber acara, jika Anda ingin membalikkan tindakan ini, semua peristiwa dalam transaksi ini akan memiliki id korelasi / penyebab sebagai TransferMoneyCommand asli yang dapat Anda gunakan untuk meningkatkan acara untuk operasi undo / pembalikan.

Jangan ragu untuk menyarankan masalah atau potensi perbaikan di atas.

Shayan C
sumber