Dalam Domain Driven Design, tampaknya ada banyak dari kesepakatan yang Entitas tidak boleh akses Repositori langsung.
Apakah ini berasal dari buku Eric Evans Domain Driven Design , atau apakah itu datang dari tempat lain?
Di mana ada beberapa penjelasan yang bagus untuk alasan di baliknya?
sunting: Untuk memperjelas: Saya tidak berbicara tentang praktik OO klasik memisahkan akses data ke dalam lapisan terpisah dari logika bisnis - Saya berbicara tentang pengaturan khusus di mana dalam DDD, Entitas tidak seharusnya berbicara dengan data lapisan akses sama sekali (yaitu mereka tidak seharusnya memegang referensi ke objek Repositori)
pembaruan: Saya memberikan hadiah kepada BacceSR karena jawabannya tampak paling dekat, tapi saya masih cukup gelap dalam hal ini. Jika itu prinsip yang begitu penting, pasti ada beberapa artikel bagus tentang itu di suatu tempat online, tentunya?
pembaruan: Maret 2013, suara positif pada pertanyaan menyiratkan ada banyak minat dalam hal ini, dan meskipun ada banyak jawaban, saya masih berpikir ada ruang untuk lebih jika orang memiliki ide tentang ini.
sumber
Jawaban:
Ada sedikit kebingungan di sini. Repositori mengakses akar agregat. Akar agregat adalah entitas. Alasan untuk ini adalah pemisahan keprihatinan dan layering yang baik. Ini tidak masuk akal pada proyek-proyek kecil, tetapi jika Anda berada di tim besar Anda ingin mengatakan, "Anda mengakses produk melalui Gudang Produk. Produk adalah akar agregat untuk kumpulan entitas, termasuk objek ProductCatalog. Jika Anda ingin memperbarui ProductCatalog Anda harus pergi melalui ProductRepository. "
Dengan cara ini Anda memiliki pemisahan yang sangat, sangat jelas pada logika bisnis dan di mana hal-hal diperbarui. Anda tidak memiliki anak yang keluar sendiri dan menulis seluruh program ini yang melakukan semua hal rumit ini ke katalog produk dan ketika datang untuk mengintegrasikannya ke proyek hulu, Anda duduk di sana memandangnya dan menyadarinya semua harus dibuang. Ini juga berarti ketika orang bergabung dengan tim, menambahkan fitur baru, mereka tahu ke mana harus pergi dan bagaimana menyusun program.
Tapi tunggu! Repositori juga merujuk ke lapisan persistensi, seperti dalam Pola Repositori. Dalam dunia yang lebih baik Repositori Eric Evans dan Pola Repositori akan memiliki nama yang terpisah, karena mereka cenderung tumpang tindih sedikit. Untuk mendapatkan pola repositori, Anda memiliki kontras dengan cara lain di mana data diakses, dengan bus layanan atau sistem model kejadian. Biasanya ketika Anda sampai ke level ini, definisi Repositori Eric Evans berjalan di sisi jalan dan Anda mulai berbicara tentang konteks yang dibatasi. Setiap konteks terikat pada dasarnya adalah aplikasi sendiri. Anda mungkin memiliki sistem persetujuan yang canggih untuk memasukkan barang ke dalam katalog produk. Dalam desain asli Anda, produk adalah bagian utama tetapi dalam konteks terbatas ini katalog produk. Anda masih dapat mengakses informasi produk dan memperbarui produk melalui bus layanan,
Kembali ke pertanyaan awal Anda. Jika Anda mengakses repositori dari dalam suatu entitas itu berarti entitas tersebut benar-benar bukan entitas bisnis tetapi mungkin sesuatu yang harus ada di lapisan layanan. Ini karena entitas adalah objek bisnis dan harus memperhatikan dirinya sendiri sebanyak DSL (bahasa spesifik domain) sebanyak mungkin. Hanya memiliki informasi bisnis di lapisan ini. Jika Anda memecahkan masalah kinerja, Anda harus mencari di tempat lain karena hanya informasi bisnis yang ada di sini. Jika tiba-tiba, Anda memiliki masalah aplikasi di sini, Anda membuatnya sangat sulit untuk memperpanjang dan memelihara aplikasi, yang sebenarnya adalah jantung dari DDD: membuat perangkat lunak yang dapat dirawat.
Tanggapan terhadap Komentar 1 : Benar, pertanyaan yang bagus. Jadi tidak semua validasi terjadi di lapisan domain. Sharp memiliki atribut "DomainSignature" yang melakukan apa yang Anda inginkan. Ini adalah ketekunan yang disadari, tetapi menjadi atribut membuat lapisan domain tetap bersih. Ini memastikan bahwa Anda tidak memiliki entitas duplikat dengan, dalam contoh Anda nama yang sama.
Tetapi mari kita bicara tentang aturan validasi yang lebih rumit. Katakanlah Anda adalah Amazon.com. Pernahkah Anda memesan sesuatu dengan kartu kredit kedaluwarsa? Saya punya, di mana saya belum memperbarui kartu dan membeli sesuatu. Ia menerima pesanan dan UI memberi tahu saya bahwa semuanya sangat bagus. Sekitar 15 menit kemudian, saya akan menerima email yang mengatakan ada masalah dengan pesanan saya, kartu kredit saya tidak valid. Apa yang terjadi di sini adalah bahwa, idealnya, ada beberapa validasi regex di lapisan domain. Apakah ini nomor kartu kredit yang benar? Jika ya, tetap ada pesanan. Namun, ada validasi tambahan di lapisan tugas aplikasi, di mana layanan eksternal diminta untuk melihat apakah pembayaran dapat dilakukan pada kartu kredit. Jika tidak, jangan mengirim barang, tunda pesanan, dan tunggu pelanggan.
Jangan takut untuk membuat objek validasi pada lapisan layanan yang dapat mengakses repositori. Jauhkan dari lapisan domain.
sumber
Pada awalnya, saya yakin bahwa beberapa entitas saya dapat mengakses repositori (mis. Pemuatan malas tanpa ORM). Kemudian saya sampai pada kesimpulan bahwa saya tidak boleh dan bahwa saya bisa menemukan cara alternatif:
Vernon Vaughn dalam buku merah Implementing Domain-Driven Design mengacu pada masalah ini di dua tempat yang saya ketahui (catatan: buku ini sepenuhnya didukung oleh Evans seperti yang dapat Anda baca dalam kata pengantar). Dalam Bab 7 tentang Layanan, ia menggunakan layanan domain dan spesifikasi untuk mengatasi kebutuhan agregat untuk menggunakan repositori dan agregat lain untuk menentukan apakah pengguna diautentikasi. Dia dikutip mengatakan:
Vernon, Vaughn (2013-02-06). Menerapkan Desain Berbasis Domain (Kindle Location 6089). Pendidikan Pearson. Edisi menyalakan.
Dan di Bab 10 tentang Agregat, di bagian berjudul "Navigasi Model" katanya (tepat setelah dia merekomendasikan penggunaan ID unik global untuk referensi akar agregat lainnya):
Dia menunjukkan contoh ini dalam kode:
Dia selanjutnya juga menyebutkan solusi lain tentang bagaimana layanan domain dapat digunakan dalam metode perintah Agregat bersama dengan pengiriman ganda . (Saya tidak bisa merekomendasikan cukup seberapa bermanfaat untuk membaca bukunya. Setelah Anda lelah dari mencari-cari di internet, membayar lebih dari uang yang layak dan membaca buku.)
Saya kemudian berdiskusi dengan Marco Pivetta @Ocramius yang selalu ramah yang menunjukkan kepada saya sedikit kode untuk mengeluarkan spesifikasi dari domain dan menggunakannya:
1) Ini tidak dianjurkan:
2) Dalam layanan domain, ini bagus:
sumber
getFriends()
sebelum melakukan hal lain, itu akan kosong atau malas dimuat. Jika kosong, maka objek ini bohong dan dalam kondisi tidak valid. Ada pemikiran tentang ini?Ini pertanyaan yang sangat bagus. Saya akan menantikan beberapa diskusi tentang ini. Tapi saya pikir itu disebutkan dalam beberapa buku DDD dan Jimmy nilssons dan Eric Evans. Saya kira itu juga terlihat melalui contoh bagaimana menggunakan pola reposistory.
TAPI mari kita bahas. Saya pikir pemikiran yang sangat valid adalah mengapa entitas harus tahu tentang bagaimana bertahan entitas lain? Penting dengan DDD adalah bahwa setiap entitas memiliki tanggung jawab untuk mengelola "lingkup pengetahuan" sendiri dan tidak boleh tahu apa-apa tentang cara membaca atau menulis entitas lain. Tentu Anda mungkin bisa saja menambahkan antarmuka repositori ke Entity A untuk membaca Entities B. Tetapi risikonya adalah Anda mengekspos pengetahuan tentang cara bertahan B. Akankah entitas A juga melakukan validasi pada B sebelum mempertahankan B ke db?
Seperti yang Anda lihat entitas A dapat lebih terlibat ke dalam siklus hidup entitas B dan yang dapat menambah kompleksitas pada model.
Saya kira (tanpa contoh) bahwa pengujian unit akan lebih kompleks.
Tapi saya yakin akan selalu ada skenario di mana Anda tergoda untuk menggunakan repositori melalui entitas. Anda harus melihat setiap skenario untuk membuat penilaian yang valid. Pro dan kontra. Tetapi solusi repositori-entitas menurut saya dimulai dengan banyak Kontra. Ini harus menjadi skenario yang sangat istimewa dengan Pro yang menyeimbangkan ....
sumber
Mengapa memisahkan akses data?
Dari buku ini, saya pikir dua halaman pertama dari bab Model Driven Design memberikan beberapa alasan mengapa Anda ingin mengaburkan rincian implementasi teknis dari penerapan model domain.
Tampaknya ini semua untuk tujuan menghindari "model analisis" terpisah yang menjadi terpisah dari implementasi sistem yang sebenarnya.
Dari apa yang saya mengerti tentang buku ini, dikatakan "model analisis" ini dapat dirancang tanpa mempertimbangkan implementasi perangkat lunak. Setelah pengembang mencoba menerapkan model yang dipahami oleh sisi bisnis, mereka membentuk abstraksi mereka sendiri karena kebutuhan, menyebabkan dinding dalam komunikasi dan pemahaman.
Di arah lain, pengembang yang memperkenalkan terlalu banyak masalah teknis ke dalam model domain dapat menyebabkan kesenjangan ini juga.
Jadi Anda dapat mempertimbangkan bahwa mempraktikkan pemisahan masalah seperti kegigihan dapat membantu melindungi terhadap desain ini dan model analisis yang berbeda. Jika dirasa perlu untuk memperkenalkan hal-hal seperti ketekunan ke dalam model maka itu adalah bendera merah. Mungkin modelnya tidak praktis untuk implementasi.
Mengutip:
"Model tunggal mengurangi kemungkinan kesalahan, karena desain sekarang merupakan hasil langsung dari model yang dipertimbangkan dengan hati-hati. Desain, dan bahkan kode itu sendiri, memiliki komunikasi model."
Cara saya menafsirkan ini, jika Anda berakhir dengan lebih banyak baris kode berurusan dengan hal-hal seperti akses database, Anda kehilangan komunikasi itu.
Jika kebutuhan untuk mengakses database adalah untuk hal-hal seperti memeriksa keunikan, lihat:
Udi Dahan: kesalahan terbesar yang dilakukan tim saat menerapkan DDD
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
di bawah "Semua aturan tidak dibuat sama"
dan
Menggunakan Pola Model Domain
http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119
di bawah "Skenario untuk Tidak Menggunakan Model Domain", yang menyentuh subjek yang sama.
Cara memisahkan akses data
Memuat data melalui antarmuka
"Lapisan akses data" telah diabstraksi melalui antarmuka, yang Anda panggil untuk mengambil data yang diperlukan:
Pro: Antarmuka memisahkan kode pipa "akses data", memungkinkan Anda untuk tetap menulis tes. Akses data dapat ditangani berdasarkan kasus per kasus memungkinkan kinerja yang lebih baik daripada strategi generik.
Cons: Kode panggilan harus menganggap apa yang telah dimuat dan apa yang belum.
Katakanlah GetOrderLines mengembalikan objek OrderLine dengan properti ProductInfo nol karena alasan kinerja. Pengembang harus memiliki pengetahuan mendalam tentang kode di balik antarmuka.
Saya sudah mencoba metode ini di sistem nyata. Anda akhirnya mengubah ruang lingkup apa yang dimuat sepanjang waktu dalam upaya untuk memperbaiki masalah kinerja. Anda akhirnya mengintip di belakang antarmuka untuk melihat kode akses data untuk melihat apa yang sedang dan tidak dimuat.
Sekarang, pemisahan kekhawatiran harus memungkinkan pengembang untuk fokus pada satu aspek kode pada satu waktu, sebanyak mungkin. Teknik antarmuka menghapus BAGAIMANA data ini dimuat, tetapi tidak BAGAIMANA BANYAK data dimuat, KAPAN dimuat, dan DI MANA itu dimuat.
Kesimpulan: Pemisahan yang sangat rendah!
Pemuatan Malas
Data dimuat sesuai permintaan. Panggilan untuk memuat data disembunyikan di dalam objek grafik itu sendiri, di mana mengakses properti dapat menyebabkan kueri sql untuk dieksekusi sebelum mengembalikan hasilnya.
Pro: 'KAPAN, DI MANA, dan BAGAIMANA' dari akses data disembunyikan dari pengembang yang berfokus pada logika domain. Tidak ada kode dalam agregat yang berhubungan dengan memuat data. Jumlah data yang dimuat dapat menjadi jumlah persis yang dibutuhkan oleh kode.
Cons: Ketika Anda terkena masalah kinerja, sulit untuk memperbaiki ketika Anda memiliki solusi "satu ukuran cocok untuk semua" generik. Pemuatan malas dapat menyebabkan kinerja yang lebih buruk secara keseluruhan, dan menerapkan pemuatan malas mungkin sulit.
Role Interface / Eager Fetching
Setiap use case dibuat eksplisit melalui Role Interface yang diimplementasikan oleh kelas agregat, yang memungkinkan strategi pemuatan data ditangani per use case.
Strategi pengambilan mungkin terlihat seperti ini:
Maka agregat Anda dapat terlihat seperti:
Strategi BillOrderFetching digunakan untuk membangun agregat, dan kemudian agregat melakukan tugasnya.
Pros: Memungkinkan untuk kode khusus per kasus penggunaan, memungkinkan untuk kinerja yang optimal. Sejalan dengan Prinsip Segregasi Antarmuka . Tidak ada persyaratan kode yang rumit. Tes unit gabungan tidak harus meniru strategi pemuatan. Strategi pemuatan umum dapat digunakan untuk sebagian besar kasus (mis. Strategi "muat semua") dan strategi pemuatan khusus dapat diterapkan bila perlu.
Cons: Pengembang masih harus menyesuaikan / meninjau strategi pengambilan setelah mengubah kode domain.
Dengan pendekatan strategi pengambilan Anda mungkin masih menemukan diri Anda mengubah kode pengambilan kustom untuk perubahan aturan bisnis. Ini bukan pemisahan keprihatinan yang sempurna tetapi akan berakhir lebih dapat dipertahankan dan lebih baik daripada opsi pertama. Strategi pengambilan memang merangkum CARA, KAPAN dan DI MANA data dimuat. Ini memiliki pemisahan keprihatinan yang lebih baik, tanpa kehilangan fleksibilitas seperti satu ukuran cocok untuk semua pendekatan pemuatan malas.
sumber
Saya menemukan blog ini memiliki argumen yang cukup bagus terhadap enkapsulasi Repositori di dalam Entitas:
http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities
sumber
Pertanyaan yang sangat bagus. Saya berada di jalur penemuan yang sama, dan sebagian besar jawaban di internet tampaknya membawa banyak masalah karena mereka memberikan solusi.
Jadi (dengan risiko menulis sesuatu yang saya tidak setuju dengan satu tahun dari sekarang) di sini adalah penemuan saya sejauh ini.
Pertama-tama, kami menyukai model domain yang kaya , yang memberi kami kemampuan menemukan yang tinggi (dari apa yang dapat kami lakukan dengan agregat) dan keterbacaan (panggilan metode ekspresif).
Kami ingin mencapai ini tanpa menyuntikkan layanan apa pun ke konstruktor entitas, karena:
Lalu, bagaimana kita bisa melakukan ini? Kesimpulan saya sejauh ini adalah bahwa dependensi metode dan pengiriman ganda memberikan solusi yang layak.
CreateCreditNote()
sekarang membutuhkan layanan yang bertanggung jawab untuk membuat catatan kredit. Ini menggunakan pengiriman ganda , sepenuhnya membongkar pekerjaan ke layanan yang bertanggung jawab, sambil mempertahankan kemampuan penemuan dariInvoice
entitas.SetStatus()
sekarang memiliki ketergantungan sederhana pada pencatat, yang jelas akan melakukan bagian dari pekerjaan .Untuk yang terakhir, untuk membuat segalanya lebih mudah pada kode klien, kita mungkin malah login melalui
IInvoiceService
. Lagi pula, pembuatan faktur tampaknya cukup intrinsik dengan faktur. Seperti singleIInvoiceService
membantu menghindari kebutuhan untuk semua jenis layanan mini untuk berbagai operasi. Kelemahannya adalah menjadi tidak jelas apa tepatnya layanan yang akan dilakukan . Bahkan mungkin mulai terlihat seperti pengiriman ganda, sementara sebagian besar pekerjaan masih dilakukan denganSetStatus()
sendirinya.Kami masih bisa memberi nama parameter 'logger', dengan harapan dapat mengungkapkan maksud kami. Tampaknya agak lemah.
Sebagai gantinya, saya akan memilih untuk meminta
IInvoiceLogger
(seperti yang sudah kita lakukan dalam contoh kode) dan telahIInvoiceService
mengimplementasikan antarmuka itu. Kode klien hanya dapat menggunakan tunggalIInvoiceService
untuk semuaInvoice
metode yang meminta 'mini-service' yang sangat khusus, faktur-intrinsik, sementara metode tanda tangan masih membuat sangat jelas apa yang mereka minta.Saya perhatikan bahwa saya belum membahas repositori dengan jelas. Logger adalah atau menggunakan repositori, tetapi izinkan saya juga memberikan contoh yang lebih eksplisit. Kita dapat menggunakan pendekatan yang sama, jika repositori dibutuhkan hanya dalam satu atau dua metode.
Bahkan, ini memberikan alternatif untuk beban malas yang selalu menyusahkan .
Pembaruan: Saya telah meninggalkan teks di bawah ini untuk tujuan historis, tetapi saya sarankan untuk menghindari muatan malas 100%.
Untuk benar, beban malas berbasis properti, saya tidak sedang menggunakan konstruktor injeksi, tapi dengan cara ketekunan-bodoh.
Di satu sisi, repositori yang memuat
Invoice
dari database dapat memiliki akses gratis ke fungsi yang akan memuat catatan kredit yang sesuai, dan menyuntikkan fungsi itu ke dalamInvoice
.Di sisi lain, kode yang membuat yang sebenarnya baru
Invoice
hanya akan melewati fungsi yang mengembalikan daftar kosong:(Sebuah kebiasaan
ILazy<out T>
bisa menyingkirkan kita dari para pemeran jelekIEnumerable
, tapi itu akan menyulitkan diskusi.)Saya akan senang mendengar pendapat, preferensi, dan peningkatan Anda!
sumber
Bagi saya ini tampaknya praktik umum terkait OOD yang baik daripada khusus untuk DDD.
Alasan yang bisa saya pikirkan adalah:
sumber
Vernon Vaughn memberikan solusi:
sumber
Saya belajar kode pemrograman berorientasi objek sebelum semua lapisan gebrakan terpisah ini muncul, dan objek / kelas pertama saya DID memetakan langsung ke database.
Akhirnya, saya menambahkan lapisan perantara karena saya harus bermigrasi ke server database lain. Saya telah melihat / mendengar tentang skenario yang sama beberapa kali.
Saya pikir memisahkan akses data (alias "Repositori") dari logika bisnis Anda, adalah salah satu dari hal-hal itu, yang telah diciptakan kembali beberapa kali, kata buku Desain Domain Driven, membuatnya banyak "noise".
Saat ini saya menggunakan 3 lapisan (GUI, Logika, Akses Data), seperti yang dilakukan banyak pengembang, karena tekniknya bagus.
Memisahkan data, menjadi
Repository
layer (aliasData Access
layer), dapat dilihat sebagai teknik pemrograman yang baik, bukan hanya aturan, untuk diikuti.Seperti banyak metodologi, Anda mungkin ingin memulai, dengan TIDAK diimplementasikan, dan pada akhirnya, memperbarui program Anda, begitu Anda memahaminya.
Quote: Iliad tidak sepenuhnya ditemukan oleh Homer, Carmina Burana tidak sepenuhnya ditemukan oleh Carl Orff, dan dalam kedua kasus, orang yang membuat karya orang lain, semuanya bekerja, mendapat pujian ;-)
sumber
Itu barang lama. Buku Eric hanya membuatnya sedikit lebih ramai.
Alasannya sederhana - pikiran manusia menjadi lemah ketika dihadapkan dengan berbagai konteks yang samar-samar terkait. Mereka mengarah pada ambiguitas (Amerika di Amerika Selatan / Utara berarti Amerika Selatan / Utara), ambiguitas mengarah pada pemetaan informasi yang konstan setiap kali pikiran "menyentuhnya" dan itu disimpulkan sebagai produktivitas dan kesalahan yang buruk.
Logika bisnis harus tercermin sejelas mungkin. Kunci asing, normalisasi, pemetaan relasional objek berasal dari domain yang sama sekali berbeda - hal-hal tersebut bersifat teknis, terkait komputer.
Dalam analogi: jika Anda belajar cara menulis, Anda tidak harus terbebani dengan pemahaman di mana pena dibuat, mengapa tinta menempel di kertas, ketika kertas ditemukan dan apa saja penemuan Cina terkenal lainnya.
Alasannya masih sama dengan yang saya sebutkan di atas. Ini dia hanya selangkah lebih maju. Mengapa entitas harus secara persisten tidak tahu apa-apa jika mereka bisa (setidaknya dekat) sepenuhnya? Berkurangnya kekhawatiran yang tidak berhubungan dengan domain yang dimiliki oleh model kami - semakin banyak ruang bernafas yang dimiliki pikiran kami ketika harus menafsirkannya kembali.
sumber
Untuk mengutip Carolina Lilientahl, "Pola harus mencegah siklus" https://www.youtube.com/watch?v=eJjadzMRQAk , di mana ia merujuk pada dependensi siklus antar kelas. Dalam hal repositori di dalam agregat, ada godaan untuk membuat dependensi siklik dari pengaturan navigasi objek sebagai satu-satunya alasan. Pola yang disebutkan di atas oleh pemrogram, yang direkomendasikan oleh Vernon Vaughn, di mana agregat lain direferensikan oleh id alih-alih instance root, (apakah ada nama untuk pola ini?) Menyarankan alternatif yang mungkin memandu ke solusi lain.
Contoh ketergantungan siklik antar kelas (pengakuan):
(Waktu0): Dua kelas, Sampel dan Sumur, merujuk satu sama lain (ketergantungan siklik). Sumur mengacu pada Sampel, dan Sampel merujuk kembali ke Sumur, karena nyaman (kadang-kadang perulangan sampel, terkadang perulangan semua sumur di piring). Saya tidak bisa membayangkan kasus di mana Sampel tidak akan merujuk kembali ke Sumur tempat ia ditempatkan.
(Time1): Setahun kemudian, banyak kasus penggunaan diimplementasikan .... dan sekarang ada kasus di mana Sampel tidak boleh merujuk kembali ke Sumur yang ditempatkan. Terdapat pelat sementara dalam langkah kerja. Di sini sebuah sumur mengacu pada sampel, yang pada gilirannya merujuk ke sebuah sumur di piring lain. Karena itu, perilaku aneh kadang-kadang terjadi ketika seseorang mencoba menerapkan fitur baru. Butuh waktu untuk menembus.
Saya juga terbantu oleh artikel ini yang disebutkan di atas tentang aspek negatif dari pemuatan malas.
sumber
Di dunia ideal, DDD mengusulkan agar Entitas tidak boleh memiliki referensi ke lapisan data. tapi kita tidak hidup di dunia ideal. Domain mungkin perlu merujuk ke objek domain lain untuk logika bisnis yang dengannya mereka mungkin tidak memiliki ketergantungan. Adalah logis bagi entitas untuk merujuk ke lapisan repositori untuk tujuan hanya baca, untuk mengambil nilai.
sumber