Saya telah mengadaptasi CQRS 1 orang miskin untuk beberapa waktu sekarang karena saya suka fleksibilitasnya untuk memiliki data granular di satu toko data, memberikan kemungkinan besar untuk analisis dan dengan demikian meningkatkan nilai bisnis dan ketika diperlukan yang lain untuk bacaan yang berisi data yang didenormalkan untuk meningkatkan kinerja .
Tetapi sayangnya sejak awal saya telah bergumul dengan masalah di mana tepatnya saya harus menempatkan logika bisnis dalam arsitektur jenis ini.
Dari apa yang saya mengerti, perintah adalah sarana untuk mengomunikasikan niat dan tidak memiliki ikatan dengan domain dengan sendirinya. Mereka pada dasarnya adalah data (bodoh - jika Anda mau) objek transfer. Ini untuk membuat perintah mudah ditransfer antara berbagai teknologi. Hal yang sama berlaku untuk acara sebagai tanggapan terhadap acara yang berhasil diselesaikan.
Dalam aplikasi DDD yang khas, logika bisnis berada di dalam entitas, objek nilai, akar agregat, mereka kaya akan data maupun perilaku. Tetapi perintah bukanlah objek domain sehingga tidak boleh terbatas pada representasi data domain, karena itu terlalu banyak membebani mereka.
Jadi pertanyaan sebenarnya adalah: Di mana tepatnya logikanya?
Saya menemukan bahwa saya cenderung menghadapi perjuangan ini paling sering ketika mencoba untuk membangun agregat yang cukup rumit yang menetapkan beberapa aturan tentang kombinasi nilai-nilainya. Juga, ketika memodelkan objek domain saya suka mengikuti paradigma gagal-cepat , tahu kapan objek mencapai metode itu dalam keadaan valid.
Katakanlah agregat Car
menggunakan dua komponen:
Transmission
,Engine
.
Baik Transmission
dan Engine
objek nilai direpresentasikan sebagai tipe super dan memiliki sub tipe sesuai, Automatic
dan Manual
transmisi, atau Petrol
dan Electric
mesin masing-masing.
Dalam domain ini, tinggal sendiri yang berhasil dibuat Transmission
, baik itu Automatic
atau Manual
, atau salah satu dari jenis Engine
itu sepenuhnya baik-baik saja. Tetapi Car
agregat memperkenalkan beberapa aturan baru, hanya berlaku ketika Transmission
dan Engine
objek digunakan dalam konteks yang sama. Yaitu:
- Ketika mobil menggunakan
Electric
mesin, satu-satunya jenis transmisi yang diizinkan adalahAutomatic
. - Ketika mobil menggunakan
Petrol
mesin, ia mungkin memiliki salah satu tipeTransmission
.
Saya bisa menangkap pelanggaran kombinasi komponen ini pada tingkat membuat perintah, tetapi seperti yang telah saya nyatakan sebelumnya, dari apa yang saya pahami yang tidak boleh dilakukan karena perintah itu kemudian akan berisi logika bisnis yang harus dibatasi pada lapisan domain.
Salah satu opsi adalah untuk memindahkan validasi logika bisnis ini ke perintah validator itu sendiri, tetapi ini tampaknya juga tidak benar. Rasanya seperti saya akan mendekonstruksi perintah, memeriksa propertinya diambil menggunakan getter dan membandingkannya dengan validator dan memeriksa hasilnya. Itu menjerit seperti pelanggaran terhadap hukum Demeter bagi saya.
Membuang opsi validasi yang disebutkan karena sepertinya tidak layak, sepertinya seseorang harus menggunakan perintah dan membangun agregat darinya. Tetapi di mana logika ini seharusnya ada? Haruskah itu berada di dalam penangan perintah yang bertanggung jawab untuk menangani perintah konkret? Atau haruskah itu mungkin dalam validator perintah (saya juga tidak suka pendekatan ini)?
Saat ini saya menggunakan perintah dan membuat agregat darinya di dalam penangan perintah yang bertanggung jawab. Tetapi ketika saya melakukan ini, haruskah saya memiliki validator perintah itu tidak akan mengandung apa-apa, karena jika CreateCar
perintah itu ada maka akan berisi komponen yang saya tahu valid pada kasus-kasus terpisah tetapi agregat mungkin mengatakan berbeda.
Mari kita bayangkan skenario berbeda yang memadukan proses validasi yang berbeda - membuat pengguna baru menggunakan CreateUser
perintah.
Perintah berisi Id
dari pengguna yang akan dibuat dan mereka Email
.
Sistem menyatakan aturan berikut untuk alamat email pengguna:
- harus unik,
- tidak boleh kosong,
- harus memiliki paling banyak 100 karakter (panjang maksimal kolom db).
Dalam hal ini, walaupun memiliki email unik adalah aturan bisnis, mengeceknya secara agregat sangat tidak masuk akal, karena saya perlu memuat seluruh rangkaian email saat ini di sistem ke memori dan memeriksa email di perintah terhadap agregat ( Eeeek! Sesuatu, sesuatu, kinerja.). Karena itu, saya akan memindahkan pemeriksaan ini ke validator perintah, yang akan menggunakan UserRepository
dependensi dan menggunakan repositori untuk memeriksa apakah pengguna dengan email yang ada dalam perintah sudah ada.
Ketika sampai pada hal ini, tiba-tiba masuk akal untuk memasukkan dua aturan email lainnya ke dalam validator perintah juga. Tapi saya merasa aturan harus benar-benar hadir dalam User
agregat dan bahwa validator perintah hanya harus memeriksa tentang keunikan dan jika validasi berhasil saya harus melanjutkan untuk membuat User
agregat dalam CreateUserCommandHandler
dan meneruskannya ke repositori untuk disimpan.
Saya merasa seperti ini karena metode penyimpanan repositori cenderung menerima agregat yang memastikan bahwa setelah agregat disahkan semua invarian dipenuhi. Ketika logika (mis. Ketidak-kekosongan) hanya hadir dalam validasi perintah itu sendiri programmer lain dapat sepenuhnya melewatkan validasi ini dan memanggil metode save UserRepository
dengan User
objek secara langsung yang dapat menyebabkan kesalahan database fatal, karena email mungkin memiliki sudah terlalu lama.
Bagaimana Anda secara pribadi menangani validasi dan transformasi yang kompleks ini? Saya sebagian besar senang dengan solusi saya, tetapi saya merasa saya perlu penegasan bahwa ide dan pendekatan saya tidak sepenuhnya bodoh untuk cukup senang dengan pilihan. Saya sepenuhnya terbuka untuk pendekatan yang sama sekali berbeda. Jika Anda memiliki sesuatu yang secara pribadi telah Anda coba dan bekerja dengan sangat baik untuk Anda, saya akan senang melihat solusi Anda.
1 Bekerja sebagai pengembang PHP yang bertanggung jawab untuk menciptakan sistem RESTful interpretasi saya terhadap CQRS sedikit menyimpang dari pendekatan pemrosesan async-command standar , seperti kadang-kadang mengembalikan hasil dari perintah karena kebutuhan memproses perintah secara serempak.
CommandDispatcher
.Jawaban:
Jawaban berikut adalah dalam konteks gaya CQRS yang dipromosikan oleh cqrs.nu di mana perintah tiba langsung pada agregat. Dalam gaya arsitektural ini, layanan aplikasi digantikan oleh komponen infrastruktur ( CommandDispatcher ) yang mengidentifikasi agregat, memuatnya, mengirimkannya perintah dan kemudian melanjutkan agregat (sebagai serangkaian acara jika Sumber pemasukan digunakan).
Ada beberapa jenis logika (validasi). Gagasan umum adalah untuk mengeksekusi logika sedini mungkin - gagal cepat jika Anda mau. Jadi, situasinya adalah sebagai berikut:
isValid
metode tetapi ini tampaknya tidak ada gunanya bagi saya karena seseorang harus ingat untuk memanggil metode ini padahal sebenarnya perintah instantiasi yang berhasil sudah cukup.command validators
, kelas yang memiliki tanggung jawab untuk memvalidasi perintah. Saya menggunakan validasi semacam ini ketika saya perlu memeriksa informasi dari beberapa agregat atau sumber eksternal. Anda bisa menggunakan ini untuk memeriksa keunikan nama pengguna.Command validators
dapat memiliki dependensi yang disuntikkan, seperti repositori. Perlu diingat bahwa validasi ini pada akhirnya konsisten dengan agregat (yaitu ketika pengguna dibuat, pengguna lain dengan nama pengguna yang sama dapat dibuat sementara itu)! Juga, jangan mencoba untuk menempatkan logika di sini yang seharusnya berada di dalam agregat! Validator perintah berbeda dari para manajer Sagas / Proses yang menghasilkan perintah berdasarkan peristiwa.When a car uses Electric engine the only allowed transmission type is Automatic
harus diperiksa di sini.Menggunakan teknik di atas tidak ada yang dapat membuat perintah yang tidak valid atau memotong logika di dalam agregat. Validator perintah secara otomatis dimuat + dipanggil oleh
CommandDispatcher
sehingga tidak ada yang dapat mengirim perintah langsung ke agregat. Seseorang dapat memanggil metode pada agregat yang melewati perintah tetapi tidak dapat bertahan dari perubahan sehingga tidak ada gunanya / tidak berbahaya untuk melakukannya.Saya juga seorang programmer PHP dan saya tidak mengembalikan apa pun dari penangan perintah saya (metode agregat dalam bentuk
handleSomeCommand
). Namun, saya cukup sering mengembalikan informasi ke klien / browser diHTTP response
, misalnya ID dari agregat root yang baru dibuat atau sesuatu dari model-baca tetapi saya tidak pernah mengembalikan (benar - benar tidak pernah ) apa pun dari metode perintah agregat saya. Fakta sederhana bahwa perintah itu diterima (dan diproses - kita berbicara tentang pemrosesan PHP sinkron, kan ?!) sudah cukup.Kami mengembalikan sesuatu ke browser (dan masih mengerjakan buku CQRS ) karena CQRS bukan arsitektur tingkat tinggi .
Contoh cara kerja validator perintah:
sumber
EmailAddress
objek nilai yang memvalidasi dirinya sendiri.EmailAddress
untuk mengurangi duplikasi. Namun yang lebih penting, dengan melakukan itu Anda juga akan memindahkan logika dari perintah Anda ke domain Anda. Perlu dicatat bahwa ini bisa terlalu jauh. Seringkali pengetahuan yang serupa (objek bernilai) mungkin memiliki persyaratan validasi yang berbeda tergantung pada siapa yang menggunakannya.EmailAddress
adalah contoh yang nyaman karena seluruh konsepsi nilai ini memiliki persyaratan validasi global.UserCanPlaceOrdersOnlyIfHeIsNotLockedValidator
. Anda dapat melihat bahwa ini adalah domain terpisah dari Pesanan sehingga tidak dapat divalidasi oleh OrderAggregate itu sendiri.Satu premis mendasar dari DDD adalah bahwa model domain memvalidasi diri mereka sendiri. Ini adalah konsep penting karena mengangkat domain Anda sebagai pihak yang bertanggung jawab untuk memastikan aturan bisnis Anda ditegakkan. Itu juga membuat model domain Anda sebagai fokus pengembangan.
Sistem CQRS (seperti yang Anda tunjukkan dengan benar) adalah detail implementasi yang mewakili sub-domain umum yang mengimplementasikan mekanisme kohesifnya sendiri. Model Anda seharusnya tidak bergantung pada infrastruktur CQRS apa pun untuk berperilaku sesuai dengan aturan bisnis Anda. Tujuan DDD adalah untuk memodelkan perilaku suatu sistem sedemikian rupa sehingga hasilnya adalah abstraksi yang berguna dari persyaratan fungsional domain bisnis inti Anda. Memindahkan setiap bagian dari perilaku ini dari model Anda, betapapun menggoda, mengurangi integritas dan kohesi model Anda (dan membuatnya kurang berguna).
Cukup dengan memperluas contoh Anda untuk memasukkan
ChangeEmail
perintah, kami dapat dengan sempurna menggambarkan mengapa Anda tidak ingin ada logika bisnis Anda dalam infrastruktur perintah Anda karena Anda perlu menduplikasi aturan Anda:Jadi sekarang kita bisa yakin bahwa logika kita perlu berada di domain kita, mari kita atasi masalah "di mana". Dua aturan pertama dapat dengan mudah diterapkan pada
User
agregat kita , tetapi aturan terakhir itu sedikit lebih bernuansa; yang membutuhkan pengetahuan lebih lanjut untuk mendapatkan wawasan yang lebih dalam. Di permukaan, sepertinya aturan ini berlaku untuk aUser
, tetapi sebenarnya tidak. "Keunikan" email berlaku untuk koleksiUsers
(sesuai dengan cakupan tertentu).Ah ha! Dengan pemikiran itu, menjadi sangat jelas bahwa Anda
UserRepository
(koleksi in-memory AndaUsers
) mungkin menjadi kandidat yang lebih baik untuk menegakkan invarian ini. Metode "save" kemungkinan merupakan tempat paling masuk akal untuk memasukkan cek (tempat Anda dapat memberikanUserEmailAlreadyExists
pengecualian). Atau, sebuah domainUserService
dapat dibuat bertanggung jawab untuk membuat yang baruUsers
dan memperbarui atributnya.Gagal cepat adalah pendekatan yang baik, tetapi hanya bisa dilakukan di mana dan kapan itu cocok dengan sisa model. Mungkin sangat menggoda untuk memeriksa parameter pada metode layanan aplikasi (atau perintah) sebelum memproses lebih lanjut dalam upaya untuk menangkap kegagalan ketika Anda (pengembang) tahu panggilan akan gagal di suatu tempat lebih dalam dalam proses. Tetapi dengan melakukan hal itu, Anda akan memiliki duplikat (dan kebocoran) pengetahuan dengan cara yang kemungkinan akan membutuhkan lebih dari satu pembaruan pada kode ketika aturan bisnis berubah.
sumber