Tampaknya ada kesepakatan luas dalam komunitas OOP bahwa konstruktor kelas tidak boleh meninggalkan objek sebagian atau bahkan sepenuhnya diinisialisasi.
Apa yang saya maksud dengan "inisialisasi"? Secara kasar, proses atom yang membawa objek yang baru dibuat ke dalam keadaan di mana semua kelas invariannya bertahan. Ini harus menjadi hal pertama yang terjadi pada suatu objek, (seharusnya hanya berjalan sekali per objek,) dan tidak ada yang diizinkan untuk mendapatkan objek yang tidak diinisialisasi. (Demikianlah saran yang sering dilakukan untuk melakukan inisialisasi objek tepat di konstruktor kelas. Untuk alasan yang sama,
Initialize
metode sering disukai, karena ini memecah atomicity dan memungkinkan untuk mendapatkan, dan menggunakan, objek yang belum dalam kondisi terdefinisi dengan baik.)
Masalah: Ketika CQRS dikombinasikan dengan event sourcing (CQRS + ES), di mana semua perubahan keadaan suatu objek terperangkap dalam serangkaian peristiwa yang terurut (aliran acara), saya bertanya-tanya kapan suatu objek benar-benar mencapai kondisi diinisialisasi penuh: Di akhir konstruktor kelas, atau setelah peristiwa pertama diterapkan pada objek?
Catatan: Saya menahan diri untuk tidak menggunakan istilah "root agregat". Jika Anda suka, gantilah setiap kali Anda membaca "objek".
Contoh untuk diskusi: Asumsikan bahwa setiap objek diidentifikasi secara unik oleh beberapa Id
nilai buram (pikirkan GUID). Aliran peristiwa yang mewakili perubahan keadaan objek itu dapat diidentifikasi di toko peristiwa dengan nilai yang sama Id
: (Jangan khawatir tentang urutan peristiwa yang benar.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Asumsikan lebih lanjut bahwa ada dua jenis objek Customer
dan ShoppingCart
. Mari kita fokus pada ShoppingCart
: Ketika dibuat, kereta belanja kosong dan harus dikaitkan dengan tepat satu pelanggan. Bit terakhir itu adalah kelas invarian: ShoppingCart
Objek yang tidak terkait dengan a Customer
dalam keadaan tidak valid.
Dalam OOP tradisional, orang mungkin memodelkan ini dalam konstruktor:
partial class ShoppingCart
{
public Id Id { get; private set; }
public Customer Customer { get; private set; }
public ShoppingCart(Id id, Customer customer)
{
this.Id = id;
this.Customer = customer;
}
}
Namun saya bingung bagaimana memodelkan ini dalam CQRS + ES tanpa berakhir dengan inisialisasi ditangguhkan. Karena inisialisasi sederhana ini secara efektif merupakan perubahan keadaan, bukankah harus dimodelkan sebagai suatu peristiwa ?:
partial class CreatedEmptyShoppingCart
{
public ShoppingCartId { get; private set; }
public CustomerId { get; private set; }
}
// Note: `ShoppingCartId` is not actually required, since that Id must be
// known in advance in order to fetch the event stream from the event store.
Ini jelas harus menjadi peristiwa pertama dalam ShoppingCart
aliran peristiwa objek apa pun , dan objek itu hanya akan diinisialisasi setelah acara diterapkan.
Jadi jika inisialisasi menjadi bagian dari "pemutaran" aliran acara (yang merupakan proses yang sangat umum yang kemungkinan akan bekerja sama, baik untuk Customer
objek atau ShoppingCart
objek atau jenis objek lainnya dalam hal ini) ...
- Haruskah konstruktor menjadi parameter-kurang dan tidak melakukan apa-apa, membiarkan semua pekerjaan untuk beberapa
void Apply(CreatedEmptyShoppingCart)
metode (yang hampir sama dengan yang disukaiInitialize()
)? - Atau haruskah konstruktor menerima aliran peristiwa dan memutarnya (yang membuat inisialisasi atom lagi, tetapi berarti konstruktor masing-masing kelas berisi logika generik yang sama "putar & terapkan", yaitu duplikasi kode yang tidak diinginkan)?
- Atau haruskah ada konstruktor OOP tradisional (seperti yang ditunjukkan di atas) yang menginisialisasi objek dengan benar, dan kemudian semua peristiwa tetapi yang pertama
void Apply(…)
terkait dengannya?
Saya tidak mengharapkan jawaban untuk menyediakan implementasi demo yang berfungsi penuh; Saya sudah sangat senang jika seseorang bisa menjelaskan di mana alasan saya cacat, atau apakah inisialisasi objek benar - benar merupakan "titik nyeri" di sebagian besar implementasi CQRS + ES.
Initialize
dihuni oleh konstruktor agregat (+ mungkin suatu metode). Itu mengarahkan saya ke pertanyaan, seperti apa pabrik Anda nantinya?Menurut pendapat saya, saya pikir jawabannya lebih seperti saran Anda # 2 untuk agregat yang ada; untuk agregat baru lebih seperti # 3 (tetapi memproses acara seperti yang Anda sarankan).
Ini beberapa kode, semoga bisa membantu.
sumber
Yah, acara pertama harus bahwa pelanggan membuat keranjang belanja , jadi ketika keranjang belanja dibuat Anda sudah memiliki id pelanggan sebagai bagian dari acara tersebut.
Jika suatu keadaan berlaku di antara dua peristiwa yang berbeda, menurut definisi itu adalah keadaan yang valid. Jadi, jika Anda mengatakan bahwa keranjang belanja yang valid dikaitkan dengan pelanggan, ini berarti Anda harus memiliki info pelanggan saat membuat kereta itu sendiri
sumber
CreatedEmptyShoppingCart
acara di pertanyaan saya: Ini memiliki informasi pelanggan, seperti yang Anda sarankan. Pertanyaan saya lebih tentang bagaimana peristiwa semacam itu berhubungan dengan, atau bersaing dengan, konstruktor kelasShoppingCart
selama implementasi.Catatan: jika Anda tidak ingin menggunakan root agregat, "entitas" mencakup sebagian besar dari apa yang Anda tanyakan di sini sementara sisi melangkah kekhawatiran tentang batas-batas transaksi.
Inilah cara lain untuk memikirkannya: entitas adalah identitas + negara. Tidak seperti nilai, entitas adalah orang yang sama bahkan ketika kondisinya berubah.
Tetapi negara itu sendiri dapat dianggap sebagai objek nilai. Maksud saya, negara tidak dapat diubah; sejarah entitas adalah transisi dari satu keadaan tidak berubah ke yang berikutnya - setiap transisi berhubungan dengan suatu peristiwa dalam aliran peristiwa.
Metode onEvent () adalah query, tentu saja - currentState tidak berubah sama sekali, sebaliknya currentState menghitung argumen yang digunakan untuk membuat nextState.
Mengikuti model ini, semua contoh Keranjang Belanja dapat dianggap sebagai mulai dari nilai benih yang sama ....
Pemisahan masalah - ShoppingCart memproses perintah untuk mencari tahu acara apa yang harus terjadi selanjutnya; Negara ShoppingCart tahu cara menuju ke negara bagian berikutnya.
sumber