Saya telah mengadaptasi desain berbasis domain selama sekitar 8 tahun sekarang dan bahkan setelah bertahun-tahun, masih ada satu hal, yang telah mengganggu saya. Itu memeriksa catatan unik dalam penyimpanan data terhadap objek domain.
Pada September 2013 Martin Fowler menyebutkan prinsip TellDon'tAsk , yang, jika mungkin, harus diterapkan ke semua objek domain, yang kemudian harus mengembalikan pesan, bagaimana operasi berjalan (dalam desain berorientasi objek ini sebagian besar dilakukan melalui pengecualian, ketika operasi tidak berhasil).
Proyek saya biasanya dibagi menjadi banyak bagian, di mana dua di antaranya adalah Domain (berisi aturan bisnis dan tidak ada yang lain, domain tersebut benar-benar tidak peduli) dan Layanan. Layanan yang mengetahui tentang lapisan repositori yang digunakan untuk data CRUD.
Karena keunikan atribut yang dimiliki suatu objek adalah aturan domain / bisnis, itu harus panjang untuk modul domain, jadi aturannya persis di tempat seharusnya.
Untuk dapat memeriksa keunikan catatan, Anda perlu meminta dataset saat ini, biasanya database, untuk mencari tahu, apakah catatan lain dengan katakanlah Name
sudah ada.
Mempertimbangkan lapisan domain adalah kegigihan dan tidak tahu bagaimana cara mengambil data tetapi hanya bagaimana melakukan operasi pada mereka, itu tidak dapat benar-benar menyentuh repositori itu sendiri.
Desain yang saya adaptasi terlihat seperti ini:
class ProductRepository
{
// throws Repository.RecordNotFoundException
public Product GetBySKU(string sku);
}
class ProductCrudService
{
private ProductRepository pr;
public ProductCrudService(ProductRepository repository)
{
pr = repository;
}
public void SaveProduct(Domain.Product product)
{
try {
pr.GetBySKU(product.SKU);
throw Service.ProductWithSKUAlreadyExistsException("msg");
} catch (Repository.RecordNotFoundException e) {
// suppress/log exception
}
pr.MarkFresh(product);
pr.ProcessChanges();
}
}
Hal ini menyebabkan layanan menentukan aturan domain daripada lapisan domain itu sendiri dan Anda memiliki aturan yang tersebar di beberapa bagian kode Anda.
Saya menyebutkan prinsip TellDon'tAsk, karena seperti yang Anda lihat dengan jelas, layanan ini menawarkan tindakan (baik menyimpan Product
atau melempar pengecualian), tetapi di dalam metode ini Anda mengoperasikan objek dengan menggunakan pendekatan prosedural.
Solusi yang jelas adalah membuat Domain.ProductCollection
kelas dengan Add(Domain.Product)
metode melempar ProductWithSKUAlreadyExistsException
, tetapi kurang dalam kinerja banyak, karena Anda akan perlu mendapatkan semua Produk dari penyimpanan data untuk mengetahui dalam kode, apakah suatu Produk sudah memiliki SKU yang sama sebagai Produk yang ingin Anda tambahkan.
Bagaimana kalian memecahkan masalah khusus ini? Ini bukan masalah sebenarnya, saya telah memiliki lapisan layanan yang mewakili aturan domain tertentu selama bertahun-tahun. Lapisan layanan biasanya juga melayani operasi domain yang lebih kompleks, saya hanya ingin tahu apakah Anda telah menemukan solusi yang lebih baik, lebih terpusat, selama karir Anda.
Jawaban:
Saya tidak setuju dengan bagian ini. Terutama kalimat terakhir.
Meskipun benar bahwa domain harus tetap bodoh, ia tahu bahwa ada "Koleksi entitas domain". Dan bahwa ada aturan domain yang menyangkut koleksi ini secara keseluruhan. Keunikan menjadi salah satunya. Dan karena implementasi logika aktual sangat bergantung pada mode persistensi spesifik, harus ada semacam abstraksi dalam domain yang menentukan kebutuhan untuk logika ini.
Jadi sesederhana membuat antarmuka yang dapat meminta jika nama sudah ada, yang kemudian diimplementasikan di penyimpanan data Anda dan dipanggil oleh siapa pun yang perlu tahu apakah nama itu unik.
Dan saya ingin menekankan bahwa repositori adalah layanan DOMAIN. Mereka adalah abstraksi di sekitar kegigihan. Ini adalah implementasi repositori yang harus dipisahkan dari domain. Sama sekali tidak ada yang salah dengan entitas domain yang memanggil layanan domain. Tidak ada yang salah dengan satu entitas dapat menggunakan repositori untuk mengambil entitas lain atau mengambil beberapa informasi tertentu, yang tidak dapat dengan mudah disimpan dalam memori. Ini adalah alasan mengapa Repositori adalah konsep kunci dalam buku Evans .
sumber
Domain.ProductCollection
saya pikirkan, mengingat mereka bertanggung jawab untuk mengambil objek dari lapisan Domain?Domain.ProductCollection
tetapi meminta repositori sebagai gantinya, sama dengan bertanya, apakahDomain.ProductCollection
berisi Produk dengan melewati SKU, kali ini, sekali lagi, meminta repositori sebagai gantinya (ini sebenarnya adalah contoh), yang alih-alih mengulangi produk-produk yang dimuat sebelumnya meminta basis data yang mendasarinya. Saya tidak bermaksud menyimpan semua Produk dalam memori kecuali saya harus, melakukannya akan menjadi omong kosong.IProductRepository
antarmukaDoesNameExist
, jelas apa yang harus dilakukan metode ini. Tetapi memastikan bahwa Produk tidak dapat memiliki nama yang sama dengan produk yang ada harus berada di domain di suatu tempat.Anda perlu membaca Greg Young pada set validasi .
Jawaban singkat: sebelum Anda melangkah terlalu jauh ke sarang tikus, Anda harus memastikan bahwa Anda memahami nilai persyaratan dari perspektif bisnis. Betapa mahalnya, untuk mendeteksi dan mengurangi duplikasi itu, daripada mencegahnya?
Jawaban yang lebih panjang: Saya telah melihat menu kemungkinan, tetapi mereka semua memiliki pengorbanan.
Anda dapat memeriksa duplikat sebelum mengirim perintah ke domain. Ini dapat dilakukan di klien, atau di layanan (contoh Anda menunjukkan teknik). Jika Anda tidak puas dengan logika yang bocor dari lapisan domain, Anda dapat mencapai hasil yang sama dengan a
DomainService
.Tentu saja, dilakukan dengan cara ini implementasi DeduplicationService akan perlu tahu sesuatu tentang cara mencari skus yang ada. Jadi sementara itu mendorong beberapa pekerjaan kembali ke domain, Anda masih dihadapkan dengan masalah dasar yang sama (membutuhkan jawaban untuk validasi yang ditetapkan, masalah dengan kondisi balapan).
Anda dapat melakukan validasi di lapisan kegigihan Anda sendiri. Database relasional sangat bagus dalam menetapkan validasi. Beri batasan keunikan pada kolom sku produk Anda, dan Anda siap melakukannya. Aplikasi hanya menyimpan produk ke dalam repositori, dan Anda mendapatkan pelanggaran kendala yang muncul kembali jika ada masalah. Jadi kode aplikasi terlihat bagus, dan kondisi balapan Anda dieliminasi, tetapi Anda memiliki "domain" peraturan bocor.
Anda dapat membuat agregat terpisah di domain Anda yang mewakili set skus yang dikenal. Saya dapat memikirkan dua variasi di sini.
Salah satunya adalah seperti ProductCatalog; produk ada di tempat lain, tetapi hubungan antara produk dan skus dikelola oleh katalog yang menjamin keunikan sku. Bukan berarti ini menyiratkan bahwa produk tidak memiliki skus; skus ditugaskan oleh ProductCatalog (jika Anda perlu skus menjadi unik, Anda mencapainya dengan hanya memiliki agregat ProductCatalog tunggal). Tinjau bahasa di mana-mana dengan pakar domain Anda - jika hal seperti itu ada, ini bisa menjadi pendekatan yang tepat.
Alternatif adalah sesuatu yang lebih mirip layanan reservasi sku. Mekanisme dasarnya sama: sebuah agregat tahu tentang semua skus, sehingga dapat mencegah pengenalan duplikat. Tetapi mekanismenya sedikit berbeda: Anda memperoleh sewa sku sebelum menugaskannya ke suatu produk; saat membuat produk, Anda menyerahkannya ke sku. Masih ada kondisi balapan dalam permainan (agregat yang berbeda, karena itu transaksi berbeda), tapi rasanya berbeda. Kelemahan nyata di sini adalah bahwa Anda memproyeksikan ke dalam model domain layanan leasing tanpa benar-benar memiliki pembenaran dalam bahasa domain.
Anda dapat menarik semua entitas produk menjadi satu agregat - yaitu, katalog produk yang dijelaskan di atas. Anda benar-benar mendapatkan keunikan skus ketika Anda melakukan ini, tetapi biayanya adalah pertengkaran tambahan, memodifikasi produk apa pun benar-benar berarti memodifikasi seluruh katalog.
Mungkin Anda tidak perlu melakukannya. Jika Anda menguji sku dengan filter Bloom , Anda dapat menemukan banyak skus unik tanpa memuat set sama sekali.
Jika use case Anda memungkinkan Anda untuk arbitrer tentang skus mana yang Anda tolak, Anda dapat menyadap semua positif palsu (bukan masalah besar jika Anda mengizinkan klien untuk menguji skus yang mereka usulkan sebelum mengirimkan perintah). Itu akan memungkinkan Anda untuk menghindari memuat set ke memori.
(Jika Anda ingin lebih menerima, Anda dapat mempertimbangkan untuk malas memuat skus jika terjadi kecocokan dalam filter bloom; Anda masih berisiko mengambil semua skus ke dalam memori kadang-kadang, tetapi seharusnya tidak menjadi kasus umum jika Anda mengizinkan kode klien untuk memeriksa perintah untuk kesalahan sebelum mengirim).
sumber