Apakah Agregat DDD benar-benar ide yang baik dalam Aplikasi Web?


40

Saya masuk ke Domain Driven Design dan beberapa konsep yang saya temui sangat masuk akal, tetapi ketika saya memikirkannya lebih banyak, saya harus bertanya-tanya apakah itu benar-benar ide yang bagus.

Konsep Agregat, misalnya masuk akal. Anda membuat domain kepemilikan kecil sehingga Anda tidak harus berurusan dengan seluruh model domain.

Namun, ketika saya memikirkan hal ini dalam konteks aplikasi web, kita sering menekan basis data untuk menarik sebagian kecil bagian data. Misalnya, sebuah halaman hanya dapat mencantumkan jumlah pesanan, dengan tautan untuk mengklik untuk membuka pesanan dan melihat nomor pesanannya.

Jika aku pemahaman Agregat benar, saya biasanya akan menggunakan pola repositori untuk mengembalikan OrderAggregate yang akan berisi para anggota GetAll, GetByID, Delete, dan Save. Ok, kedengarannya bagus. Tapi...

Jika saya memanggil GetAll untuk mendaftar semua pesanan saya, tampaknya bagi saya bahwa pola ini akan membutuhkan seluruh daftar informasi agregat untuk dikembalikan, menyelesaikan pesanan, memesan baris, dll ... Ketika saya hanya memerlukan sebagian kecil dari informasi itu (hanya informasi tajuk).

Apakah saya melewatkan sesuatu? Atau ada beberapa tingkat optimasi yang akan Anda gunakan di sini? Saya tidak dapat membayangkan bahwa seseorang akan menganjurkan mengembalikan seluruh agregat informasi ketika Anda tidak membutuhkannya.

Tentu saja, orang bisa membuat metode seperti repositori Anda GetOrderHeaders, tetapi itu tampaknya mengalahkan tujuan menggunakan pola seperti repositori.

Adakah yang bisa menjelaskan ini untuk saya?

EDIT:

Setelah penelitian lebih lanjut, saya pikir pemutusan di sini adalah bahwa pola Repositori murni berbeda dari apa yang kebanyakan orang pikirkan tentang Repositori.

Fowler mendefinisikan repositori sebagai penyimpan data yang menggunakan semantik koleksi, dan umumnya disimpan dalam memori. Ini berarti membuat grafik seluruh objek.

Evans mengubah Repositori untuk memasukkan Agregat Roots, dan repositori diamputasi untuk hanya mendukung objek dalam Agregat.

Kebanyakan orang tampaknya menganggap repositori sebagai Objek Akses Data yang dimuliakan, di mana Anda hanya membuat metode untuk mendapatkan data apa pun yang Anda inginkan. Itu tampaknya tidak menjadi maksud seperti yang dijelaskan dalam Pola Arsitektur Aplikasi Perusahaan Fowler.

Yang lain lagi menganggap repositori sebagai abstraksi sederhana yang digunakan terutama untuk membuat pengujian dan ejekan lebih mudah, atau untuk memisahkan kegigihan dari sisa sistem.

Saya kira jawabannya adalah bahwa ini adalah konsep yang jauh lebih kompleks daripada yang saya pikirkan sebelumnya.


4
"Saya kira jawabannya adalah bahwa ini adalah konsep yang jauh lebih kompleks daripada yang saya pikirkan sebelumnya." Ini sangat benar.
quentin-starin

untuk situasi Anda, Anda dapat membuat proxy untuk objek root agregat yang secara selektif mengambil dan menyimpan data hanya saat diminta
Steven A. Lowe

Saran saya adalah menerapkan lazy load di asosiasi agregat root. Jadi, Anda dapat mengambil daftar root tanpa memuat terlalu banyak objek.
Juliano

4
hampir 6 tahun kemudian, masih merupakan pertanyaan yang bagus. Setelah membaca bab dalam buku merah, saya akan mengatakan: jangan membuat agregat Anda terlalu besar. Sangat menggoda untuk memilih konsep tingkat atas dari domain Anda dan mendeklarasikannya Root to Rule Them All tetapi DDD menganjurkan agregat yang lebih kecil. Dan mengurangi ketidakefisienan seperti yang Anda jelaskan di atas.
Cpt. Senkfuss

Agregat Anda harus sekecil mungkin sambil tetap alami dan efektif untuk domain (tantangan!). Selain itu, repo Anda sangat bagus dan diinginkan untuk memiliki metode yang sangat spesifik .
Timo

Jawaban:


30

Jangan gunakan Model Domain Anda dan agregat untuk kueri.

Sebenarnya, apa yang Anda tanyakan adalah pertanyaan yang cukup umum bahwa seperangkat prinsip dan pola telah ditetapkan untuk menghindari hal itu. Itu disebut CQRS .


2
@Mystere Man: Tidak, itu adalah untuk menyediakan data minimal yang diperlukan. Itulah salah satu tujuan besar model baca yang terpisah. Ini juga membantu mengatasi beberapa masalah konkurensi di muka. CQRS memiliki beberapa manfaat ketika diterapkan pada DDD.
quentin-starin

2
@Mystere: Saya cukup terkejut Anda akan melewatkannya jika Anda membaca artikel yang saya tautkan. Lihat bagian berjudul "Permintaan (pelaporan)": "Ketika suatu aplikasi meminta data ... ini harus dilakukan dalam satu panggilan tunggal ke lapisan Permintaan dan sebagai imbalannya akan mendapatkan satu DTO tunggal berisi semua data yang dibutuhkan. .. Alasan untuk ini adalah bahwa data biasanya ditanyakan berkali-kali lebih banyak daripada perilaku domain yang dieksekusi, jadi dengan mengoptimalkan ini Anda akan meningkatkan kinerja yang dirasakan dari sistem. "
quentin-starin

4
Itulah sebabnya kami tidak akan menggunakan Repositori untuk membaca data dalam sistem CQRS. Kami akan menulis kelas sederhana untuk merangkum kueri (menggunakan teknologi apa pun yang nyaman atau dibutuhkan, seringkali ADO.Net atau Linq2Sql atau SubSonic cocok di sini), mengembalikan hanya (semua) data yang diperlukan untuk tugas yang sedang dikerjakan, untuk menghindari menyeret data melalui semua lapisan normal dari Repositori DDD. Kami hanya akan menggunakan Gudang untuk mengambil Agregat jika kami ingin mengirim perintah ke domain.
quentin-starin

9
"Aku tidak bisa membayangkan ada orang yang menganjurkan mengembalikan seluruh agregat informasi ketika kamu tidak membutuhkannya." Saya mencoba mengatakan bahwa Anda benar dengan pernyataan ini. Jangan mengambil seluruh kumpulan informasi ketika Anda tidak membutuhkannya. Ini adalah inti dari CQRS yang diterapkan pada DDD. Anda tidak perlu agregat untuk kueri. Dapatkan data melalui mekanisme yang berbeda, dan kemudian lakukan secara konsisten.
quentin-starin

2
@ qes Memang, solusi terbaik adalah tidak menggunakan DDD untuk permintaan (baca) :) Tapi Anda masih menggunakan DDD di bagian Command, yaitu untuk menyimpan atau memperbarui data. Jadi saya punya pertanyaan untuk Anda, apakah Anda selalu menggunakan Repositori dengan Entitas ketika Anda perlu memperbarui data dalam DB? Katakanlah Anda perlu mengubah hanya satu nilai kecil di kolom (beralih semacam), apakah Anda masih memuat seluruh Entitas di lapisan Aplikasi, mengubah satu nilai (properti) dan kemudian menyimpan seluruh Entitas kembali ke DB? Sedikit berlebihan juga?
Andrew

8

Saya berjuang, dan masih berjuang, dengan cara terbaik menggunakan pola repositori dalam Desain Domain Driven. Setelah menggunakannya sekarang untuk pertama kalinya, saya menemukan praktik berikut:

  1. Repositori harus sederhana; itu hanya bertanggung jawab untuk menyimpan objek domain dan mengambilnya. Semua logika lain harus di objek lain, seperti pabrik dan layanan domain.

  2. Repositori berperilaku seperti koleksi seolah-olah itu adalah kumpulan memori dari akar agregat.

  3. Repositori bukan DAO generik, masing-masing repositori memiliki antarmuka yang unik dan sempit. Repositori sering memiliki metode pencari spesifik yang memungkinkan Anda untuk mencari koleksi dalam hal domain (misalnya: beri saya semua pesanan terbuka untuk pengguna X). Repositori itu sendiri dapat diimplementasikan dengan bantuan DAO generik.

  4. Idealnya metode finder hanya akan mengembalikan akar agregat. Jika itu tidak efisien, ia juga dapat mengembalikan objek nilai hanya baca daripada berisi apa yang Anda butuhkan (meskipun ini merupakan nilai tambah jika objek nilai ini juga dapat dinyatakan dalam domain). Sebagai upaya terakhir repositori juga dapat digunakan untuk mengembalikan himpunan bagian atau koleksi himpunan bagian dari agregat root.

  5. Pilihan seperti ini bergantung pada teknologi yang digunakan, karena Anda perlu menemukan cara untuk paling efisien mengekspresikan model domain Anda dengan teknologi yang digunakan.


Ini jelas merupakan subjek yang kompleks. Sulit untuk mengubah teori menjadi praktik terutama ketika menggabungkan dua teori yang berbeda dan terpisah menjadi satu praktik.
Sebastian Patten

6

Saya tidak berpikir metode GetOrderHeaders Anda mengalahkan tujuan repositori sama sekali.

DDD prihatin (antara lain) dengan memastikan bahwa Anda mendapatkan apa yang Anda butuhkan melalui agregat root (misalnya, Anda tidak akan memiliki Repositori OrderDetailsRepository), tetapi itu tidak membatasi Anda dengan cara yang Anda sebutkan.

Jika OrderHeader adalah konsep Domain, maka Anda harus mendefinisikannya dan memiliki metode penyimpanan yang tepat untuk mengambilnya. Pastikan saja Anda melakukan root agregat yang benar saat melakukannya.


Mungkin saya bingung konsep di sini, tetapi pemahaman saya tentang pola repositori adalah untuk memisahkan ketekunan dari domain, dengan menggunakan antarmuka standar untuk ketekunan. Jika Anda harus menambahkan metode khusus untuk fitur tertentu, itu sepertinya menyatukan kembali hal-hal.
Erik Funkenbusch

1
Mekanisme kegigihan dipisahkan dari domain, tetapi tidak apa yang sedang berlangsung. Jika Anda mendapati diri Anda mengatakan hal-hal seperti "kami perlu mendaftar Header Pesanan di sini", maka Anda perlu memodelkan OrderHeader di Domain Anda dan menyediakan cara untuk mengambilnya dari repositori Anda.
Eric King

Juga, jangan terpaku pada "antarmuka standar untuk kegigihan". Tidak ada yang namanya pola penyimpanan generik yang cukup untuk semua aplikasi yang memungkinkan. Setiap aplikasi akan memiliki banyak metode penyimpanan di luar standar "GetById", "Save", dll. Metode tersebut adalah titik awal, bukan titik akhir.
Eric King

4

Penggunaan DDD saya mungkin tidak dianggap DDD "murni" tapi saya telah mengadaptasi strategi dunia nyata berikut menggunakan DDD terhadap penyimpanan data DB.

  • Root agregat memiliki repositori terkait
  • Repositori terkait hanya digunakan oleh root agregat itu (tidak tersedia untuk umum)
  • Repositori dapat berisi panggilan permintaan (mis. GetAllActiveOrders, GetOrderItemsForOrder)
  • Layanan memperlihatkan subset publik dari repositori dan operasi non-crud lainnya (mis. Transfer Uang dari satu rekening bank ke yang lain, LoadById, Cari / Temukan, BuatEntitas, dll.).
  • Saya menggunakan Root -> Layanan -> tumpukan Repositori. Layanan DDD hanya dianggap digunakan untuk apa pun yang tidak dapat dijawab Entitas (mis. LoadById, TransferMoneyFromAccountToAccount), tetapi di dunia nyata saya cenderung juga tetap menggunakan layanan terkait CRUD lainnya (Simpan, Hapus, Pertanyaan) meskipun root harus bisa "menjawab / melakukan" ini sendiri. Perhatikan bahwa tidak ada yang salah dengan memberikan entitas akses ke layanan root agregat lain! Namun, ingat Anda tidak akan memasukkan dalam layanan (GetOrderItemsForOrder) tetapi akan memasukkan itu dalam repositori sehingga Agregat Root dapat memanfaatkannya. Perhatikan bahwa suatu layanan tidak boleh mengekspos kueri terbuka seperti yang dapat dilakukan repositori.
  • Saya biasanya mendefinisikan Repositori secara abstrak dalam model domain (melalui antarmuka) dan memberikan implementasi konkret yang terpisah. Saya sepenuhnya mendefinisikan layanan dalam model domain yang menyuntikkan dalam repositori beton untuk penggunaannya.

** Anda tidak perlu mengembalikan seluruh agregat. Namun, jika Anda menginginkan lebih, Anda harus menanyakan root, bukan layanan atau repositori lain. Ini adalah pemuatan malas dan dapat dilakukan secara manual dengan pemuatan malas orang miskin (menyuntikkan repositori / layanan yang sesuai ke root) atau menggunakan dan ORM yang mendukung ini.

Dalam contoh Anda, saya mungkin akan menyediakan panggilan repositori yang hanya membawa header pesanan jika saya ingin memuat detail pada panggilan terpisah. Perhatikan bahwa dengan memiliki "OrderHeader" kami sebenarnya memperkenalkan konsep tambahan ke dalam domain.


3

Model domain Anda berisi logika bisnis Anda dalam bentuk yang paling murni. Semua hubungan dan operasi yang mendukung operasi bisnis. Apa yang Anda lewatkan dari peta konseptual Anda adalah gagasan tentang Layanan Lapisan Aplikasi lapisan layanan membungkus model domain dan memberikan tampilan yang disederhanakan dari domain bisnis (proyeksi jika Anda mau) yang memungkinkan model Domain untuk mengubah sesuai kebutuhan tanpa secara langsung memengaruhi aplikasi menggunakan lapisan layanan.

Melangkah lebih jauh Gagasan agregat adalah bahwa ada satu objek, akar agregat, yang bertanggung jawab untuk menjaga konsistensi agregat. Dalam contoh Anda, pesanan akan bertanggung jawab untuk memanipulasi garis pesanannya.

Sebagai contoh Anda, lapisan layanan akan memaparkan operasi seperti GetOrdersForCustomer yang hanya akan mengembalikan apa yang diperlukan untuk melihat daftar ringkasan pesanan (seperti Anda menyebutnya OrderHeaders).

Akhirnya, pola Repositori tidak hanya koleksi, tetapi juga memungkinkan untuk permintaan deklaratif. Dalam C # Anda dapat menggunakan LINQ sebagai Obyek Kueri , atau sebagian besar O / RM lainnya menyediakan spesifikasi Obyek Kueri juga.

Repositori memediasi antara domain dan lapisan pemetaan data, bertindak seperti kumpulan objek domain dalam memori. Objek Klien membuat spesifikasi permintaan secara deklaratif dan mengirimkannya ke Repositori untuk kepuasan. (dari halaman Repositori Fowler )

Melihat bahwa Anda dapat membuat kueri terhadap repositori, masuk akal juga untuk memberikan metode kenyamanan yang menangani kueri umum. Yaitu jika Anda hanya ingin header pesanan Anda, Anda dapat membuat kueri yang mengembalikan hanya header dan mengeksposnya dari metode kenyamanan di repositori Anda.

Semoga ini bisa membantu memperjelas hal-hal.


0

Saya tahu ini adalah pertanyaan lama, tetapi saya tampaknya memiliki jawaban yang berbeda.

Ketika saya membuat Repositori, umumnya ia membungkus beberapa kueri yang di- cache .

Fowler mendefinisikan repositori sebagai penyimpan data yang menggunakan semantik koleksi, dan umumnya disimpan dalam memori. Ini berarti membuat grafik seluruh objek.

Simpan repositori itu di ram server Anda. Mereka tidak hanya melewati objek ke database!

Jika saya dalam aplikasi web dengan pesanan daftar halaman, yang dapat Anda klik untuk melihat detailnya, kemungkinan saya ingin halaman daftar pesanan saya memiliki detail tentang pesanan (ID, Nama, Jumlah, Tanggal) untuk membantu pengguna memutuskan mana yang ingin mereka lihat.

Pada titik ini Anda memiliki dua opsi.

  1. Anda dapat melakukan query database dan menarik kembali apa yang Anda butuhkan untuk membuat listing, lalu query lagi untuk menarik detail individual yang perlu Anda lihat pada halaman detail.

  2. Anda dapat membuat 1 kueri yang menarik kembali semua informasi dan menyimpannya. Pada permintaan halaman berikutnya Anda membaca dari ram server bukan dari database. Jika pengguna menekan balik atau memilih halaman berikutnya Anda masih melakukan nol perjalanan ke database.

Pada kenyataannya bagaimana Anda menerapkannya hanya itu, dan detail implementasi. Jika pengguna terbesar saya memiliki 10 pesanan, saya mungkin ingin menggunakan opsi 2. Jika saya berbicara 10.000 pesanan maka opsi 1 diperlukan. Dalam kedua kasus di atas dan dalam banyak kasus lain saya ingin repositori menyembunyikan detail implementasi itu.

Ke depan jika saya mendapatkan tiket untuk memberi tahu pengguna berapa banyak yang telah mereka belanjakan untuk pesanan ( data teragregasi ) pada bulan lalu di halaman daftar pesanan, apakah saya lebih suka menulis logika untuk menghitungnya dalam SQL dan melakukan perjalanan bolak-balik lagi ke DB atau Anda lebih suka menghitungnya menggunakan data yang sudah ada di server ram?

Dalam pengalaman saya, agregat domain menawarkan manfaat besar.

  • Mereka adalah menggunakan kembali kode bagian besar yang benar-benar berfungsi.
  • Mereka menyederhanakan kode dengan menjaga logika bisnis Anda tepat di lapisan inti alih-alih harus menelusuri lapisan infrastruktur untuk mendapatkan server sql untuk melakukannya.
  • Mereka juga dapat secara dramatis mempercepat waktu respons Anda dengan mengurangi jumlah pertanyaan yang perlu Anda buat karena Anda dapat dengan mudah menyimpannya.
  • SQL yang saya tulis sering jauh lebih mudah dikelola karena saya sering hanya meminta semuanya dan menghitung sisi server.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.